@flowdot.ai/daemon 1.0.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.
Files changed (85) hide show
  1. package/LICENSE +45 -0
  2. package/README.md +51 -0
  3. package/dist/goals/DependencyResolver.d.ts +54 -0
  4. package/dist/goals/DependencyResolver.js +329 -0
  5. package/dist/goals/ErrorRecovery.d.ts +133 -0
  6. package/dist/goals/ErrorRecovery.js +489 -0
  7. package/dist/goals/GoalApiClient.d.ts +81 -0
  8. package/dist/goals/GoalApiClient.js +743 -0
  9. package/dist/goals/GoalCache.d.ts +65 -0
  10. package/dist/goals/GoalCache.js +243 -0
  11. package/dist/goals/GoalCommsHandler.d.ts +150 -0
  12. package/dist/goals/GoalCommsHandler.js +378 -0
  13. package/dist/goals/GoalExporter.d.ts +164 -0
  14. package/dist/goals/GoalExporter.js +318 -0
  15. package/dist/goals/GoalImporter.d.ts +107 -0
  16. package/dist/goals/GoalImporter.js +345 -0
  17. package/dist/goals/GoalManager.d.ts +110 -0
  18. package/dist/goals/GoalManager.js +535 -0
  19. package/dist/goals/GoalReporter.d.ts +105 -0
  20. package/dist/goals/GoalReporter.js +534 -0
  21. package/dist/goals/GoalScheduler.d.ts +102 -0
  22. package/dist/goals/GoalScheduler.js +209 -0
  23. package/dist/goals/GoalValidator.d.ts +72 -0
  24. package/dist/goals/GoalValidator.js +657 -0
  25. package/dist/goals/MetaGoalEnforcer.d.ts +111 -0
  26. package/dist/goals/MetaGoalEnforcer.js +536 -0
  27. package/dist/goals/MilestoneBreaker.d.ts +74 -0
  28. package/dist/goals/MilestoneBreaker.js +348 -0
  29. package/dist/goals/PermissionBridge.d.ts +109 -0
  30. package/dist/goals/PermissionBridge.js +326 -0
  31. package/dist/goals/ProgressTracker.d.ts +113 -0
  32. package/dist/goals/ProgressTracker.js +324 -0
  33. package/dist/goals/ReviewScheduler.d.ts +106 -0
  34. package/dist/goals/ReviewScheduler.js +360 -0
  35. package/dist/goals/TaskExecutor.d.ts +116 -0
  36. package/dist/goals/TaskExecutor.js +370 -0
  37. package/dist/goals/TaskFeedback.d.ts +126 -0
  38. package/dist/goals/TaskFeedback.js +402 -0
  39. package/dist/goals/TaskGenerator.d.ts +75 -0
  40. package/dist/goals/TaskGenerator.js +329 -0
  41. package/dist/goals/TaskQueue.d.ts +84 -0
  42. package/dist/goals/TaskQueue.js +331 -0
  43. package/dist/goals/TaskSanitizer.d.ts +61 -0
  44. package/dist/goals/TaskSanitizer.js +464 -0
  45. package/dist/goals/errors.d.ts +116 -0
  46. package/dist/goals/errors.js +299 -0
  47. package/dist/goals/index.d.ts +24 -0
  48. package/dist/goals/index.js +23 -0
  49. package/dist/goals/types.d.ts +395 -0
  50. package/dist/goals/types.js +230 -0
  51. package/dist/index.d.ts +4 -0
  52. package/dist/index.js +3 -0
  53. package/dist/loop/DaemonIPC.d.ts +67 -0
  54. package/dist/loop/DaemonIPC.js +358 -0
  55. package/dist/loop/IntervalParser.d.ts +39 -0
  56. package/dist/loop/IntervalParser.js +217 -0
  57. package/dist/loop/LoopDaemon.d.ts +123 -0
  58. package/dist/loop/LoopDaemon.js +1821 -0
  59. package/dist/loop/LoopExecutor.d.ts +93 -0
  60. package/dist/loop/LoopExecutor.js +326 -0
  61. package/dist/loop/LoopManager.d.ts +79 -0
  62. package/dist/loop/LoopManager.js +476 -0
  63. package/dist/loop/LoopScheduler.d.ts +69 -0
  64. package/dist/loop/LoopScheduler.js +329 -0
  65. package/dist/loop/LoopStore.d.ts +57 -0
  66. package/dist/loop/LoopStore.js +406 -0
  67. package/dist/loop/LoopValidator.d.ts +55 -0
  68. package/dist/loop/LoopValidator.js +603 -0
  69. package/dist/loop/errors.d.ts +115 -0
  70. package/dist/loop/errors.js +312 -0
  71. package/dist/loop/index.d.ts +11 -0
  72. package/dist/loop/index.js +10 -0
  73. package/dist/loop/notifications/Notifier.d.ts +28 -0
  74. package/dist/loop/notifications/Notifier.js +78 -0
  75. package/dist/loop/notifications/SlackNotifier.d.ts +28 -0
  76. package/dist/loop/notifications/SlackNotifier.js +203 -0
  77. package/dist/loop/notifications/TerminalNotifier.d.ts +18 -0
  78. package/dist/loop/notifications/TerminalNotifier.js +72 -0
  79. package/dist/loop/notifications/WebhookNotifier.d.ts +24 -0
  80. package/dist/loop/notifications/WebhookNotifier.js +123 -0
  81. package/dist/loop/notifications/index.d.ts +24 -0
  82. package/dist/loop/notifications/index.js +109 -0
  83. package/dist/loop/types.d.ts +280 -0
  84. package/dist/loop/types.js +222 -0
  85. package/package.json +92 -0
@@ -0,0 +1,111 @@
1
+ import { EventEmitter } from 'node:events';
2
+ import type { Goal, GoalHash, MetaGoal, Task, TaskType, CreateTaskInput, Logger } from './types.js';
3
+ import { GoalError } from './errors.js';
4
+ import type { TaskSuggestion } from './TaskGenerator.js';
5
+ export type MetaGoalLLMFunction = (prompt: string, options?: {
6
+ modelTier?: string;
7
+ maxTokens?: number;
8
+ temperature?: number;
9
+ }) => Promise<string>;
10
+ export interface ConstraintViolation {
11
+ readonly metaGoalId: number;
12
+ readonly constraintDescription: string;
13
+ readonly priority: number;
14
+ readonly explanation: string;
15
+ readonly severity: 'block' | 'warn';
16
+ readonly suggestedFix?: string;
17
+ }
18
+ export interface EnforcementResult {
19
+ readonly allowed: boolean;
20
+ readonly violations: ConstraintViolation[];
21
+ readonly warnings: ConstraintViolation[];
22
+ readonly modifiedTask?: TaskSuggestion | CreateTaskInput;
23
+ readonly wasModified: boolean;
24
+ readonly processingTime: number;
25
+ }
26
+ export interface ParsedConstraint {
27
+ readonly metaGoalId: number;
28
+ readonly type: ConstraintType;
29
+ readonly target: ConstraintTarget;
30
+ readonly pattern: string;
31
+ readonly priority: number;
32
+ readonly isBlocking: boolean;
33
+ }
34
+ export type ConstraintType = 'never_do' | 'always_do' | 'prefer' | 'avoid' | 'require' | 'limit' | 'time_restrict';
35
+ export type ConstraintTarget = 'task_type' | 'task_title' | 'task_description' | 'file_path' | 'command' | 'time' | 'cost' | 'frequency' | 'any';
36
+ export interface MetaGoalEnforcerOptions {
37
+ readonly logger?: Logger;
38
+ readonly llmFunction?: MetaGoalLLMFunction;
39
+ readonly cacheTtlMs?: number;
40
+ readonly useLlmForComplexConstraints?: boolean;
41
+ readonly defaultSeverity?: 'block' | 'warn';
42
+ }
43
+ export interface MetaGoalEnforcerEvents {
44
+ 'violation-detected': [ConstraintViolation];
45
+ 'task-blocked': [{
46
+ task: TaskSuggestion | CreateTaskInput;
47
+ violations: ConstraintViolation[];
48
+ }];
49
+ 'task-modified': [{
50
+ original: TaskSuggestion | CreateTaskInput;
51
+ modified: TaskSuggestion | CreateTaskInput;
52
+ }];
53
+ 'cache-updated': [{
54
+ metaGoalCount: number;
55
+ }];
56
+ 'error': [Error];
57
+ }
58
+ export declare class MetaGoalEnforcementError extends GoalError {
59
+ constructor(message: string, cause?: Error);
60
+ }
61
+ export declare class MetaGoalEnforcer extends EventEmitter<MetaGoalEnforcerEvents> {
62
+ private readonly logger;
63
+ private readonly llmFunction?;
64
+ private readonly cacheTtlMs;
65
+ private readonly useLlmForComplexConstraints;
66
+ private readonly defaultSeverity;
67
+ private constraintCache;
68
+ private cacheTimestamps;
69
+ constructor(options?: MetaGoalEnforcerOptions);
70
+ enforceOnSuggestion(suggestion: TaskSuggestion, metaGoals: MetaGoal[], goalHash: GoalHash): Promise<EnforcementResult>;
71
+ enforceOnInput(input: CreateTaskInput, metaGoals: MetaGoal[], goalHash: GoalHash): Promise<EnforcementResult>;
72
+ enforceOnTask(task: Task, metaGoals: MetaGoal[], goalHash: GoalHash): Promise<EnforcementResult>;
73
+ canGenerateTasks(goal: Goal, metaGoals: MetaGoal[]): {
74
+ allowed: boolean;
75
+ reason?: string;
76
+ };
77
+ enforceOnBatch(suggestions: TaskSuggestion[], metaGoals: MetaGoal[], goalHash: GoalHash): Promise<{
78
+ allowed: TaskSuggestion[];
79
+ blocked: Array<{
80
+ suggestion: TaskSuggestion;
81
+ violations: ConstraintViolation[];
82
+ }>;
83
+ warnings: Array<{
84
+ suggestion: TaskSuggestion;
85
+ warnings: ConstraintViolation[];
86
+ }>;
87
+ }>;
88
+ private parseConstraints;
89
+ private parseMetaGoal;
90
+ private parsewithLLM;
91
+ private checkViolations;
92
+ private checkSingleConstraint;
93
+ private matchesPattern;
94
+ private extractKeyTerms;
95
+ private filterApplicableMetaGoals;
96
+ private getCachedConstraints;
97
+ private cacheConstraints;
98
+ clearCache(): void;
99
+ getCacheStats(): {
100
+ cachedMetaGoals: number;
101
+ totalConstraints: number;
102
+ };
103
+ }
104
+ export declare function metaGoalAppliesToGoal(metaGoal: MetaGoal, goalHash: GoalHash): boolean;
105
+ export declare function sortMetaGoalsByPriority(metaGoals: MetaGoal[]): MetaGoal[];
106
+ export declare function getBlockingMetaGoalsForType(metaGoals: MetaGoal[], taskType: TaskType): MetaGoal[];
107
+ export declare function summarizeConstraints(metaGoals: MetaGoal[], goalHash: GoalHash): {
108
+ blocking: string[];
109
+ warnings: string[];
110
+ };
111
+ export declare function createMetaGoalEnforcer(options?: MetaGoalEnforcerOptions): MetaGoalEnforcer;
@@ -0,0 +1,536 @@
1
+ import { EventEmitter } from 'node:events';
2
+ import { GoalError } from './errors.js';
3
+ const noopLogger = {
4
+ debug: () => { },
5
+ info: () => { },
6
+ warn: () => { },
7
+ error: () => { },
8
+ };
9
+ const CONSTRAINT_PATTERNS = [
10
+ { regex: /never\s+(delete|remove|rm)\s+/i, type: 'never_do', target: 'command', isBlocking: true },
11
+ { regex: /never\s+(push|deploy)\s+to\s+production/i, type: 'never_do', target: 'command', isBlocking: true },
12
+ { regex: /never\s+(modify|change|edit)\s+/i, type: 'never_do', target: 'file_path', isBlocking: true },
13
+ { regex: /never\s+run\s+/i, type: 'never_do', target: 'command', isBlocking: true },
14
+ { regex: /don't\s+(ever|)\s*(delete|remove|modify)/i, type: 'never_do', target: 'any', isBlocking: true },
15
+ { regex: /prohibited|forbidden|banned/i, type: 'never_do', target: 'any', isBlocking: true },
16
+ { regex: /avoid\s+/i, type: 'avoid', target: 'any', isBlocking: false },
17
+ { regex: /try\s+not\s+to/i, type: 'avoid', target: 'any', isBlocking: false },
18
+ { regex: /minimize\s+/i, type: 'avoid', target: 'any', isBlocking: false },
19
+ { regex: /prefer\s+not\s+to/i, type: 'avoid', target: 'any', isBlocking: false },
20
+ { regex: /prefer\s+/i, type: 'prefer', target: 'any', isBlocking: false },
21
+ { regex: /prioritize\s+/i, type: 'prefer', target: 'any', isBlocking: false },
22
+ { regex: /when\s+possible,?\s+(use|prefer)/i, type: 'prefer', target: 'any', isBlocking: false },
23
+ { regex: /always\s+/i, type: 'always_do', target: 'any', isBlocking: true },
24
+ { regex: /must\s+(have|include|be)/i, type: 'require', target: 'any', isBlocking: true },
25
+ { regex: /require[sd]?\s+/i, type: 'require', target: 'any', isBlocking: true },
26
+ { regex: /limit\s+to\s+/i, type: 'limit', target: 'any', isBlocking: true },
27
+ { regex: /maximum\s+(of\s+)?\d+/i, type: 'limit', target: 'frequency', isBlocking: true },
28
+ { regex: /no\s+more\s+than\s+/i, type: 'limit', target: 'any', isBlocking: true },
29
+ { regex: /at\s+most\s+/i, type: 'limit', target: 'any', isBlocking: true },
30
+ { regex: /only\s+(during|between|after|before)/i, type: 'time_restrict', target: 'time', isBlocking: true },
31
+ { regex: /not\s+(during|between|after|before)/i, type: 'time_restrict', target: 'time', isBlocking: true },
32
+ { regex: /business\s+hours/i, type: 'time_restrict', target: 'time', isBlocking: true },
33
+ { regex: /quiet\s+hours/i, type: 'time_restrict', target: 'time', isBlocking: true },
34
+ ];
35
+ const TASK_TYPE_KEYWORDS = {
36
+ research: ['research', 'search', 'find', 'look up', 'investigate', 'explore'],
37
+ draft: ['draft', 'write', 'create content', 'compose', 'document'],
38
+ code: ['code', 'implement', 'develop', 'program', 'build', 'fix'],
39
+ execute: ['execute', 'run', 'command', 'shell', 'terminal'],
40
+ recipe: ['recipe', 'workflow', 'automation', 'pipeline'],
41
+ loop: ['loop', 'recurring', 'scheduled', 'repeat'],
42
+ notify: ['notify', 'alert', 'message', 'remind'],
43
+ };
44
+ export class MetaGoalEnforcementError extends GoalError {
45
+ constructor(message, cause) {
46
+ super('ENFORCEMENT_ERROR', message, cause ? { cause } : {});
47
+ }
48
+ }
49
+ export class MetaGoalEnforcer extends EventEmitter {
50
+ logger;
51
+ llmFunction;
52
+ cacheTtlMs;
53
+ useLlmForComplexConstraints;
54
+ defaultSeverity;
55
+ constraintCache = new Map();
56
+ cacheTimestamps = new Map();
57
+ constructor(options = {}) {
58
+ super();
59
+ this.logger = options.logger ?? noopLogger;
60
+ this.llmFunction = options.llmFunction;
61
+ this.cacheTtlMs = options.cacheTtlMs ?? 5 * 60 * 1000;
62
+ this.useLlmForComplexConstraints = options.useLlmForComplexConstraints ?? false;
63
+ this.defaultSeverity = options.defaultSeverity ?? 'warn';
64
+ }
65
+ async enforceOnSuggestion(suggestion, metaGoals, goalHash) {
66
+ const startTime = Date.now();
67
+ try {
68
+ const applicable = this.filterApplicableMetaGoals(metaGoals, goalHash);
69
+ if (applicable.length === 0) {
70
+ return {
71
+ allowed: true,
72
+ violations: [],
73
+ warnings: [],
74
+ wasModified: false,
75
+ processingTime: Date.now() - startTime,
76
+ };
77
+ }
78
+ const constraints = await this.parseConstraints(applicable);
79
+ const { violations, warnings } = await this.checkViolations(suggestion, constraints, 'suggestion');
80
+ for (const violation of violations) {
81
+ this.emit('violation-detected', violation);
82
+ }
83
+ const allowed = violations.length === 0;
84
+ if (!allowed) {
85
+ this.emit('task-blocked', { task: suggestion, violations });
86
+ }
87
+ return {
88
+ allowed,
89
+ violations,
90
+ warnings,
91
+ wasModified: false,
92
+ processingTime: Date.now() - startTime,
93
+ };
94
+ }
95
+ catch (error) {
96
+ const err = error instanceof Error ? error : new Error(String(error));
97
+ this.logger.error('META_GOAL_ENFORCER', 'Enforcement failed', { error: err.message });
98
+ this.emit('error', err);
99
+ return {
100
+ allowed: false,
101
+ violations: [{
102
+ metaGoalId: -1,
103
+ constraintDescription: 'Enforcement system error',
104
+ priority: 999,
105
+ explanation: err.message,
106
+ severity: 'block',
107
+ }],
108
+ warnings: [],
109
+ wasModified: false,
110
+ processingTime: Date.now() - startTime,
111
+ };
112
+ }
113
+ }
114
+ async enforceOnInput(input, metaGoals, goalHash) {
115
+ const startTime = Date.now();
116
+ try {
117
+ const applicable = this.filterApplicableMetaGoals(metaGoals, goalHash);
118
+ if (applicable.length === 0) {
119
+ return {
120
+ allowed: true,
121
+ violations: [],
122
+ warnings: [],
123
+ wasModified: false,
124
+ processingTime: Date.now() - startTime,
125
+ };
126
+ }
127
+ const constraints = await this.parseConstraints(applicable);
128
+ const { violations, warnings } = await this.checkViolations(input, constraints, 'input');
129
+ for (const violation of violations) {
130
+ this.emit('violation-detected', violation);
131
+ }
132
+ const allowed = violations.length === 0;
133
+ if (!allowed) {
134
+ this.emit('task-blocked', { task: input, violations });
135
+ }
136
+ return {
137
+ allowed,
138
+ violations,
139
+ warnings,
140
+ wasModified: false,
141
+ processingTime: Date.now() - startTime,
142
+ };
143
+ }
144
+ catch (error) {
145
+ const err = error instanceof Error ? error : new Error(String(error));
146
+ this.logger.error('META_GOAL_ENFORCER', 'Enforcement failed', { error: err.message });
147
+ this.emit('error', err);
148
+ return {
149
+ allowed: false,
150
+ violations: [{
151
+ metaGoalId: -1,
152
+ constraintDescription: 'Enforcement system error',
153
+ priority: 999,
154
+ explanation: err.message,
155
+ severity: 'block',
156
+ }],
157
+ warnings: [],
158
+ wasModified: false,
159
+ processingTime: Date.now() - startTime,
160
+ };
161
+ }
162
+ }
163
+ async enforceOnTask(task, metaGoals, goalHash) {
164
+ const input = {
165
+ title: task.title,
166
+ description: task.description ?? undefined,
167
+ taskType: task.taskType,
168
+ scheduledFor: task.scheduledFor ? task.scheduledFor.toISOString() : undefined,
169
+ milestoneId: task.milestoneId ?? undefined,
170
+ };
171
+ return this.enforceOnInput(input, metaGoals, goalHash);
172
+ }
173
+ canGenerateTasks(goal, metaGoals) {
174
+ const applicable = this.filterApplicableMetaGoals(metaGoals, goal.hash);
175
+ for (const metaGoal of applicable) {
176
+ const description = metaGoal.description.toLowerCase();
177
+ if (description.includes('no tasks') ||
178
+ description.includes('pause task generation') ||
179
+ description.includes('stop generating')) {
180
+ return {
181
+ allowed: false,
182
+ reason: `Meta-goal "${metaGoal.description}" prevents task generation`,
183
+ };
184
+ }
185
+ }
186
+ return { allowed: true };
187
+ }
188
+ async enforceOnBatch(suggestions, metaGoals, goalHash) {
189
+ const allowed = [];
190
+ const blocked = [];
191
+ const warnings = [];
192
+ for (const suggestion of suggestions) {
193
+ const result = await this.enforceOnSuggestion(suggestion, metaGoals, goalHash);
194
+ if (result.allowed) {
195
+ allowed.push(suggestion);
196
+ if (result.warnings.length > 0) {
197
+ warnings.push({ suggestion, warnings: result.warnings });
198
+ }
199
+ }
200
+ else {
201
+ blocked.push({ suggestion, violations: result.violations });
202
+ }
203
+ }
204
+ this.logger.debug('META_GOAL_ENFORCER', 'Batch enforcement complete', {
205
+ total: suggestions.length,
206
+ allowed: allowed.length,
207
+ blocked: blocked.length,
208
+ });
209
+ return { allowed, blocked, warnings };
210
+ }
211
+ async parseConstraints(metaGoals) {
212
+ const constraints = [];
213
+ for (const metaGoal of metaGoals) {
214
+ const cached = this.getCachedConstraints(metaGoal.id);
215
+ if (cached) {
216
+ constraints.push(...cached);
217
+ continue;
218
+ }
219
+ const parsed = await this.parseMetaGoal(metaGoal);
220
+ constraints.push(...parsed);
221
+ this.cacheConstraints(metaGoal.id, parsed);
222
+ }
223
+ constraints.sort((a, b) => b.priority - a.priority);
224
+ return constraints;
225
+ }
226
+ async parseMetaGoal(metaGoal) {
227
+ const description = metaGoal.description;
228
+ const constraints = [];
229
+ for (const pattern of CONSTRAINT_PATTERNS) {
230
+ if (pattern.regex.test(description)) {
231
+ constraints.push({
232
+ metaGoalId: metaGoal.id,
233
+ type: pattern.type,
234
+ target: pattern.target,
235
+ pattern: description,
236
+ priority: metaGoal.priority,
237
+ isBlocking: pattern.isBlocking,
238
+ });
239
+ }
240
+ }
241
+ if (constraints.length === 0 && this.useLlmForComplexConstraints && this.llmFunction) {
242
+ try {
243
+ const llmConstraints = await this.parsewithLLM(metaGoal);
244
+ constraints.push(...llmConstraints);
245
+ }
246
+ catch {
247
+ this.logger.warn('META_GOAL_ENFORCER', 'LLM parsing failed, using default', {
248
+ metaGoalId: metaGoal.id,
249
+ });
250
+ }
251
+ }
252
+ if (constraints.length === 0) {
253
+ constraints.push({
254
+ metaGoalId: metaGoal.id,
255
+ type: 'avoid',
256
+ target: 'any',
257
+ pattern: description,
258
+ priority: metaGoal.priority,
259
+ isBlocking: false,
260
+ });
261
+ }
262
+ return constraints;
263
+ }
264
+ async parsewithLLM(metaGoal) {
265
+ if (!this.llmFunction) {
266
+ return [];
267
+ }
268
+ const prompt = `Analyze this behavioral constraint and extract structured rules:
269
+
270
+ Constraint: "${metaGoal.description}"
271
+
272
+ Respond with JSON array of rules, each with:
273
+ - type: "never_do" | "always_do" | "prefer" | "avoid" | "require" | "limit" | "time_restrict"
274
+ - target: "task_type" | "task_title" | "task_description" | "file_path" | "command" | "time" | "cost" | "frequency" | "any"
275
+ - pattern: the specific thing to match
276
+ - isBlocking: true if this should block tasks, false if just a warning
277
+
278
+ Example response:
279
+ [{"type":"never_do","target":"command","pattern":"delete production","isBlocking":true}]
280
+
281
+ Only respond with the JSON array, nothing else.`;
282
+ const response = await this.llmFunction(prompt, {
283
+ modelTier: 'fast',
284
+ maxTokens: 500,
285
+ temperature: 0.1,
286
+ });
287
+ try {
288
+ const parsed = JSON.parse(response);
289
+ return parsed.map((p) => ({
290
+ metaGoalId: metaGoal.id,
291
+ type: p.type,
292
+ target: p.target,
293
+ pattern: p.pattern,
294
+ priority: metaGoal.priority,
295
+ isBlocking: p.isBlocking,
296
+ }));
297
+ }
298
+ catch {
299
+ return [];
300
+ }
301
+ }
302
+ async checkViolations(task, constraints, _taskSource) {
303
+ const violations = [];
304
+ const warnings = [];
305
+ const taskTitle = task.title.toLowerCase();
306
+ const taskDescription = ('description' in task ? task.description : '')?.toLowerCase() ?? '';
307
+ const taskType = 'taskType' in task ? task.taskType : ('type' in task ? task.type : undefined);
308
+ for (const constraint of constraints) {
309
+ const violation = this.checkSingleConstraint(constraint, taskTitle, taskDescription, taskType);
310
+ if (violation) {
311
+ if (violation.severity === 'block') {
312
+ violations.push(violation);
313
+ }
314
+ else {
315
+ warnings.push(violation);
316
+ }
317
+ }
318
+ }
319
+ return { violations, warnings };
320
+ }
321
+ checkSingleConstraint(constraint, taskTitle, taskDescription, taskType) {
322
+ const pattern = constraint.pattern.toLowerCase();
323
+ switch (constraint.type) {
324
+ case 'never_do': {
325
+ if (this.matchesPattern(pattern, taskTitle, taskDescription, taskType, constraint.target)) {
326
+ return {
327
+ metaGoalId: constraint.metaGoalId,
328
+ constraintDescription: constraint.pattern,
329
+ priority: constraint.priority,
330
+ explanation: `Task matches forbidden pattern: "${constraint.pattern}"`,
331
+ severity: 'block',
332
+ };
333
+ }
334
+ break;
335
+ }
336
+ case 'avoid': {
337
+ if (this.matchesPattern(pattern, taskTitle, taskDescription, taskType, constraint.target)) {
338
+ return {
339
+ metaGoalId: constraint.metaGoalId,
340
+ constraintDescription: constraint.pattern,
341
+ priority: constraint.priority,
342
+ explanation: `Task matches pattern that should be avoided: "${constraint.pattern}"`,
343
+ severity: 'warn',
344
+ };
345
+ }
346
+ break;
347
+ }
348
+ case 'limit': {
349
+ if (this.matchesPattern(pattern, taskTitle, taskDescription, taskType, constraint.target)) {
350
+ return {
351
+ metaGoalId: constraint.metaGoalId,
352
+ constraintDescription: constraint.pattern,
353
+ priority: constraint.priority,
354
+ explanation: `Task is subject to limit constraint: "${constraint.pattern}"`,
355
+ severity: 'warn',
356
+ };
357
+ }
358
+ break;
359
+ }
360
+ case 'time_restrict': {
361
+ const now = new Date();
362
+ const hour = now.getHours();
363
+ if (pattern.includes('quiet hours') || pattern.includes('not during')) {
364
+ if (hour >= 22 || hour < 6) {
365
+ return {
366
+ metaGoalId: constraint.metaGoalId,
367
+ constraintDescription: constraint.pattern,
368
+ priority: constraint.priority,
369
+ explanation: `Current time is within restricted hours`,
370
+ severity: constraint.isBlocking ? 'block' : 'warn',
371
+ };
372
+ }
373
+ }
374
+ if (pattern.includes('business hours') || pattern.includes('only during')) {
375
+ if (hour < 9 || hour >= 17) {
376
+ return {
377
+ metaGoalId: constraint.metaGoalId,
378
+ constraintDescription: constraint.pattern,
379
+ priority: constraint.priority,
380
+ explanation: `Current time is outside allowed hours`,
381
+ severity: constraint.isBlocking ? 'block' : 'warn',
382
+ };
383
+ }
384
+ }
385
+ break;
386
+ }
387
+ default:
388
+ if (constraint.isBlocking) {
389
+ if (this.matchesPattern(pattern, taskTitle, taskDescription, taskType, constraint.target)) {
390
+ return {
391
+ metaGoalId: constraint.metaGoalId,
392
+ constraintDescription: constraint.pattern,
393
+ priority: constraint.priority,
394
+ explanation: `Task violates constraint: "${constraint.pattern}"`,
395
+ severity: 'block',
396
+ };
397
+ }
398
+ }
399
+ }
400
+ return null;
401
+ }
402
+ matchesPattern(pattern, taskTitle, taskDescription, taskType, target) {
403
+ const terms = this.extractKeyTerms(pattern);
404
+ switch (target) {
405
+ case 'task_type': {
406
+ if (!taskType)
407
+ return false;
408
+ const typeKeywords = TASK_TYPE_KEYWORDS[taskType];
409
+ return terms.some((term) => typeKeywords.some((kw) => term.includes(kw) || kw.includes(term)));
410
+ }
411
+ case 'task_title':
412
+ return terms.some((term) => taskTitle.includes(term));
413
+ case 'task_description':
414
+ return terms.some((term) => taskDescription.includes(term));
415
+ case 'command': {
416
+ const commandIndicators = ['run', 'execute', 'command', 'shell', 'terminal'];
417
+ const isCommandTask = taskType === 'execute' ||
418
+ commandIndicators.some((i) => taskTitle.includes(i) || taskDescription.includes(i));
419
+ if (!isCommandTask)
420
+ return false;
421
+ return terms.some((term) => taskTitle.includes(term) || taskDescription.includes(term));
422
+ }
423
+ case 'file_path': {
424
+ const pathPatterns = /(?:\/[\w.-]+)+|\w+\.\w+/g;
425
+ const pathsInTitle = taskTitle.match(pathPatterns) ?? [];
426
+ const pathsInDesc = taskDescription.match(pathPatterns) ?? [];
427
+ const allPaths = [...pathsInTitle, ...pathsInDesc];
428
+ return terms.some((term) => allPaths.some((path) => path.includes(term)));
429
+ }
430
+ case 'any':
431
+ default:
432
+ return terms.some((term) => taskTitle.includes(term) ||
433
+ taskDescription.includes(term) ||
434
+ (taskType && taskType.includes(term)));
435
+ }
436
+ }
437
+ extractKeyTerms(pattern) {
438
+ const stripped = pattern
439
+ .replace(/never|always|avoid|prefer|don't|do not|must|require|limit|prohibited|forbidden/gi, '')
440
+ .replace(/\b(the|a|an|to|in|on|at|for|of|with|that|this|is|are|be|been|was|were)\b/gi, '')
441
+ .trim();
442
+ const terms = stripped
443
+ .split(/[\s,;.]+/)
444
+ .map((t) => t.toLowerCase().trim())
445
+ .filter((t) => t.length > 2);
446
+ return terms;
447
+ }
448
+ filterApplicableMetaGoals(metaGoals, goalHash) {
449
+ return metaGoals.filter((mg) => {
450
+ if (!mg.isActive)
451
+ return false;
452
+ if (mg.scope === 'all')
453
+ return true;
454
+ if (Array.isArray(mg.scope)) {
455
+ return mg.scope.includes(goalHash);
456
+ }
457
+ return true;
458
+ });
459
+ }
460
+ getCachedConstraints(metaGoalId) {
461
+ const cached = this.constraintCache.get(metaGoalId);
462
+ const timestamp = this.cacheTimestamps.get(metaGoalId);
463
+ if (!cached || !timestamp)
464
+ return null;
465
+ if (Date.now() - timestamp > this.cacheTtlMs) {
466
+ this.constraintCache.delete(metaGoalId);
467
+ this.cacheTimestamps.delete(metaGoalId);
468
+ return null;
469
+ }
470
+ return cached;
471
+ }
472
+ cacheConstraints(metaGoalId, constraints) {
473
+ this.constraintCache.set(metaGoalId, constraints);
474
+ this.cacheTimestamps.set(metaGoalId, Date.now());
475
+ this.emit('cache-updated', { metaGoalCount: this.constraintCache.size });
476
+ }
477
+ clearCache() {
478
+ this.constraintCache.clear();
479
+ this.cacheTimestamps.clear();
480
+ this.logger.debug('META_GOAL_ENFORCER', 'Cache cleared');
481
+ }
482
+ getCacheStats() {
483
+ let totalConstraints = 0;
484
+ for (const constraints of this.constraintCache.values()) {
485
+ totalConstraints += constraints.length;
486
+ }
487
+ return {
488
+ cachedMetaGoals: this.constraintCache.size,
489
+ totalConstraints,
490
+ };
491
+ }
492
+ }
493
+ export function metaGoalAppliesToGoal(metaGoal, goalHash) {
494
+ if (!metaGoal.isActive)
495
+ return false;
496
+ if (metaGoal.scope === 'all')
497
+ return true;
498
+ if (Array.isArray(metaGoal.scope)) {
499
+ return metaGoal.scope.includes(goalHash);
500
+ }
501
+ return true;
502
+ }
503
+ export function sortMetaGoalsByPriority(metaGoals) {
504
+ return [...metaGoals].sort((a, b) => b.priority - a.priority);
505
+ }
506
+ export function getBlockingMetaGoalsForType(metaGoals, taskType) {
507
+ const typeKeywords = TASK_TYPE_KEYWORDS[taskType];
508
+ return metaGoals.filter((mg) => {
509
+ if (!mg.isActive)
510
+ return false;
511
+ const description = mg.description.toLowerCase();
512
+ if (!/never|prohibited|forbidden|don't|do not/i.test(description)) {
513
+ return false;
514
+ }
515
+ return typeKeywords.some((kw) => description.includes(kw));
516
+ });
517
+ }
518
+ export function summarizeConstraints(metaGoals, goalHash) {
519
+ const blocking = [];
520
+ const warnings = [];
521
+ for (const mg of metaGoals) {
522
+ if (!metaGoalAppliesToGoal(mg, goalHash))
523
+ continue;
524
+ const description = mg.description.toLowerCase();
525
+ if (/never|prohibited|forbidden|must not/i.test(description)) {
526
+ blocking.push(mg.description);
527
+ }
528
+ else if (/avoid|prefer not|minimize/i.test(description)) {
529
+ warnings.push(mg.description);
530
+ }
531
+ }
532
+ return { blocking, warnings };
533
+ }
534
+ export function createMetaGoalEnforcer(options = {}) {
535
+ return new MetaGoalEnforcer(options);
536
+ }