@directive-run/cli 0.2.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/cli.js ADDED
@@ -0,0 +1,2992 @@
1
+ #!/usr/bin/env node
2
+ import * as p from '@clack/prompts';
3
+ import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync } from 'fs';
4
+ import { join, relative, dirname, resolve } from 'path';
5
+ import pc5 from 'picocolors';
6
+ import { getAllExamples, getExample, getKnowledge } from '@directive-run/knowledge';
7
+
8
+ // src/lib/constants.ts
9
+ var CLI_NAME = "directive";
10
+ var SECTION_START = "<!-- directive:start -->";
11
+ var SECTION_END = "<!-- directive:end -->";
12
+ var TOOL_SIGNALS = [
13
+ {
14
+ id: "cursor",
15
+ name: "Cursor",
16
+ signals: [".cursor", ".cursorrules"],
17
+ outputPath: ".cursorrules"
18
+ },
19
+ {
20
+ id: "claude",
21
+ name: "Claude Code",
22
+ signals: [".claude"],
23
+ outputPath: ".claude/CLAUDE.md"
24
+ },
25
+ {
26
+ id: "copilot",
27
+ name: "GitHub Copilot",
28
+ signals: [".github"],
29
+ outputPath: ".github/copilot-instructions.md"
30
+ },
31
+ {
32
+ id: "windsurf",
33
+ name: "Windsurf",
34
+ signals: [".windsurfrules"],
35
+ outputPath: ".windsurfrules"
36
+ },
37
+ {
38
+ id: "cline",
39
+ name: "Cline",
40
+ signals: [".clinerules"],
41
+ outputPath: ".clinerules"
42
+ }
43
+ ];
44
+ function detectTools(rootDir) {
45
+ const detected = [];
46
+ for (const tool of TOOL_SIGNALS) {
47
+ const hasSignal = tool.signals.some(
48
+ (signal) => existsSync(join(rootDir, signal))
49
+ );
50
+ if (hasSignal) {
51
+ detected.push({
52
+ name: tool.name,
53
+ id: tool.id,
54
+ outputPath: join(rootDir, tool.outputPath)
55
+ });
56
+ }
57
+ }
58
+ return detected;
59
+ }
60
+ function getToolConfig(id) {
61
+ const tool = TOOL_SIGNALS.find((t) => t.id === id);
62
+ if (!tool) {
63
+ throw new Error(`Unknown tool: ${id}`);
64
+ }
65
+ return tool;
66
+ }
67
+ function getAllToolIds() {
68
+ return TOOL_SIGNALS.map((t) => t.id);
69
+ }
70
+
71
+ // src/lib/merge.ts
72
+ function mergeSection(existingContent, newSection) {
73
+ const startIdx = existingContent.indexOf(SECTION_START);
74
+ const endIdx = existingContent.indexOf(SECTION_END);
75
+ const wrapped = `${SECTION_START}
76
+ ${newSection}
77
+ ${SECTION_END}`;
78
+ if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
79
+ return existingContent.slice(0, startIdx) + wrapped + existingContent.slice(endIdx + SECTION_END.length);
80
+ }
81
+ const separator = existingContent.endsWith("\n") ? "\n" : "\n\n";
82
+ return existingContent + separator + wrapped + "\n";
83
+ }
84
+ function hasDirectiveSection(content) {
85
+ return content.includes(SECTION_START) && content.includes(SECTION_END);
86
+ }
87
+ var MONOREPO_SIGNALS = [
88
+ { file: "pnpm-workspace.yaml", tool: "pnpm" },
89
+ { file: "turbo.json", tool: "turbo" }
90
+ ];
91
+ function detectMonorepo(startDir) {
92
+ let dir = resolve(startDir);
93
+ while (dir !== dirname(dir)) {
94
+ for (const signal of MONOREPO_SIGNALS) {
95
+ if (existsSync(join(dir, signal.file))) {
96
+ return { isMonorepo: true, rootDir: dir, tool: signal.tool };
97
+ }
98
+ }
99
+ const pkgPath = join(dir, "package.json");
100
+ if (existsSync(pkgPath)) {
101
+ try {
102
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
103
+ if (pkg.workspaces) {
104
+ const tool = existsSync(join(dir, "yarn.lock")) ? "yarn" : "npm";
105
+ return { isMonorepo: true, rootDir: dir, tool };
106
+ }
107
+ } catch {
108
+ }
109
+ }
110
+ dir = dirname(dir);
111
+ }
112
+ return { isMonorepo: false, rootDir: startDir };
113
+ }
114
+
115
+ // src/templates/claude.ts
116
+ function generateClaudeRules() {
117
+ const corePatterns = getKnowledge("core-patterns");
118
+ const antiPatterns = getKnowledge("anti-patterns");
119
+ const naming = getKnowledge("naming");
120
+ const schemaTypes = getKnowledge("schema-types");
121
+ return `# Directive \u2014 Complete AI Coding Rules
122
+
123
+ > Constraint-driven runtime for TypeScript. Declare requirements, let the runtime resolve them.
124
+ > https://directive.run | \`npm install @directive-run/core\`
125
+ > Full reference with examples: https://directive.run/llms.txt
126
+
127
+ ## Core Patterns
128
+
129
+ ${corePatterns}
130
+
131
+ ---
132
+
133
+ ## Anti-Patterns (All 36)
134
+
135
+ ${antiPatterns}
136
+
137
+ ### AI Package Anti-Patterns (21-36)
138
+
139
+ | # | WRONG | CORRECT |
140
+ |---|-------|---------|
141
+ | 21 | TS types for factsSchema: \`{ confidence: number }\` | Use \`{ confidence: t.number() }\` |
142
+ | 22 | \`facts.cache.push(item)\` in orchestrator | \`facts.cache = [...facts.cache, item]\` |
143
+ | 23 | Returning data from orchestrator \`resolve\` | Resolvers return \`void\` \u2014 mutate \`context.facts\` |
144
+ | 24 | Forgetting \`orchestrator.start()\` multi-agent | Single: implicit. Multi: must call \`start()\` |
145
+ | 25 | Catching \`Error\` not \`GuardrailError\` | \`GuardrailError\` has \`.guardrailName\`, \`.errorCode\` |
146
+ | 26 | \`from '@directive-run/ai'\` for adapters | Subpath: \`from '@directive-run/ai/openai'\` |
147
+ | 27 | Assuming \`{ input_tokens }\` structure | Normalized: \`{ inputTokens, outputTokens }\` |
148
+ | 28 | Same CircuitBreaker across agents | Create separate instances per dependency |
149
+ | 29 | \`budgetWarningThreshold: 1.5\` | Must be 0-1 (percentage) |
150
+ | 30 | \`race\` minSuccess > agents.length | Must be \`1 \u2264 minSuccess \u2264 agents.length\` |
151
+ | 31 | Async summarizer with autoManage: true | Use \`autoManage: false\` for sync control |
152
+ | 32 | Side effects in reflect \`evaluator\` | Evaluator must be pure |
153
+ | 33 | Task calling \`runSingleAgent\` | Tasks can't call agents \u2014 separate node |
154
+ | 34 | Task expecting object input | Input is always \`string\` \u2014 \`JSON.parse(input)\` |
155
+ | 35 | Task ID same as agent ID | IDs share namespace \u2014 distinct names |
156
+ | 36 | \`from '@directive-run/ai/mcp'\` | \`from '@directive-run/ai'\` (main export) |
157
+
158
+ ---
159
+
160
+ ## Naming Conventions
161
+
162
+ ${naming}
163
+
164
+ ---
165
+
166
+ ## Schema Type Builders
167
+
168
+ ${schemaTypes}
169
+
170
+ ---
171
+
172
+ ## Multi-Module Quick Reference
173
+
174
+ \`\`\`typescript
175
+ // Two modules with cross-module dependency
176
+ const authSchema = { facts: { token: t.string(), isAuth: t.boolean() } };
177
+ const authModule = createModule("auth", { schema: authSchema, /* ... */ });
178
+
179
+ const cartModule = createModule("cart", {
180
+ schema: { facts: { items: t.array<CartItem>() } },
181
+ crossModuleDeps: { auth: authSchema }, // Declare dependency
182
+ constraints: {
183
+ checkout: {
184
+ when: (facts) => facts.self.items.length > 0 && facts.auth.isAuth, // facts.self.* for own, facts.auth.* for cross
185
+ require: () => ({ type: "CHECKOUT" }),
186
+ },
187
+ },
188
+ // ...
189
+ });
190
+
191
+ const system = createSystem({ modules: { auth: authModule, cart: cartModule } });
192
+ // Access: system.facts.auth.token, system.events.cart.addItem({...})
193
+ \`\`\`
194
+
195
+ Key rules:
196
+ - \`facts.self.*\` for own module facts in constraints/resolvers
197
+ - \`facts.otherModule.*\` for cross-module reads
198
+ - \`crossModuleDeps\` must declare consumed schemas
199
+ - \`system.events.moduleName.eventName(payload)\` for namespaced events
200
+ - The \`::\` separator is internal \u2014 always use dot notation
201
+
202
+ ---
203
+
204
+ ## Constraints
205
+
206
+ - \`when(facts)\` \u2192 boolean. When true, requirement is emitted.
207
+ - \`require(facts)\` \u2192 \`{ type: "TYPE", ...data }\` object (never string literal)
208
+ - \`priority: number\` \u2014 higher evaluated first
209
+ - \`after: ["constraintName"]\` \u2014 ordering within same priority
210
+ - Async: \`async: true\` + \`deps: ['factName']\` (deps REQUIRED for async)
211
+ - Constraints DECLARE needs, resolvers FULFILL them \u2014 decoupled.
212
+
213
+ ---
214
+
215
+ ## Resolvers
216
+
217
+ - \`resolve(req, context)\` \u2014 async, returns \`void\`. Mutate \`context.facts.*\`.
218
+ - \`requirement: "TYPE"\` \u2014 which type this handles
219
+ - \`key: (req) => string\` \u2014 deduplication key
220
+ - \`retry: { attempts: 3, backoff: "exponential", initialDelay: 100 }\`
221
+ - \`batch: { maxSize: 10, windowMs: 50 }\` for N+1 prevention
222
+ - Always \`await system.settle()\` after start to wait for resolution
223
+
224
+ ---
225
+
226
+ ## System API
227
+
228
+ - \`system.facts.fieldName\` \u2014 read/write facts
229
+ - \`system.derive.derivationName\` \u2014 read derived values
230
+ - \`system.events.eventName(payload)\` \u2014 dispatch events
231
+ - \`system.subscribe(listener)\` \u2014 subscribe to all changes
232
+ - \`system.read(key)\` \u2014 read fact or derivation by string key
233
+ - \`system.inspect()\` \u2014 full state snapshot
234
+ - \`system.settle()\` \u2014 wait for async operations
235
+ - \`system.start()\` / \`system.stop()\` / \`system.destroy()\`
236
+
237
+ ---
238
+
239
+ ## React (\`@directive-run/react\`)
240
+
241
+ \`\`\`typescript
242
+ import { useSelector, useEvent } from "@directive-run/react";
243
+
244
+ const count = useSelector(system, (s) => s.facts.count);
245
+ const events = useEvent(system);
246
+ // onClick={() => events.increment()}
247
+ \`\`\`
248
+
249
+ **NO** \`useDirective()\` hook. Use \`useSelector\` + \`useEvent\`.
250
+
251
+ ---
252
+
253
+ ## Plugins (\`@directive-run/core/plugins\`)
254
+
255
+ \`devtoolsPlugin()\`, \`loggingPlugin()\`, \`persistencePlugin(config)\`,
256
+ \`createCircuitBreaker(config)\`, \`createObservability(config)\`
257
+
258
+ ---
259
+
260
+ ## AI Package (\`@directive-run/ai\`)
261
+
262
+ ### Single-Agent Orchestrator
263
+
264
+ \`\`\`typescript
265
+ import { createAgentOrchestrator, t } from '@directive-run/ai';
266
+ import { createAnthropicRunner } from '@directive-run/ai/anthropic';
267
+
268
+ const orchestrator = createAgentOrchestrator({
269
+ runner: createAnthropicRunner({ apiKey }),
270
+ factsSchema: { result: t.string(), confidence: t.number() },
271
+ init: (facts) => { facts.result = ""; facts.confidence = 0; },
272
+ maxTokenBudget: 100000,
273
+ budgetWarningThreshold: 0.8,
274
+ guardrails: { input: [...], output: [...] },
275
+ memory: createAgentMemory({ strategy: createSlidingWindowStrategy({ maxMessages: 30 }) }),
276
+ debug: true,
277
+ });
278
+
279
+ const result = await orchestrator.run(agent, "analyze this");
280
+ \`\`\`
281
+
282
+ ### Multi-Agent
283
+
284
+ \`\`\`typescript
285
+ import { createMultiAgentOrchestrator, parallel, sequential, dag } from '@directive-run/ai';
286
+
287
+ const orch = createMultiAgentOrchestrator({
288
+ agents: { researcher: agentA, writer: agentB },
289
+ patterns: {
290
+ pipeline: sequential(["researcher", "writer"]),
291
+ brainstorm: parallel(["researcher", "writer"], mergeResults),
292
+ workflow: dag([
293
+ { id: "research", handler: "researcher" },
294
+ { id: "write", handler: "writer", dependencies: ["research"] },
295
+ ]),
296
+ },
297
+ runner,
298
+ });
299
+ orch.start(); // Required for multi-agent!
300
+ \`\`\`
301
+
302
+ 8 patterns: \`parallel\`, \`sequential\`, \`supervisor\`, \`dag\`, \`reflect\`, \`race\`, \`debate\`, \`goal\`
303
+
304
+ ### Adapter Imports (Subpath!)
305
+
306
+ \`\`\`typescript
307
+ import { createAnthropicRunner } from '@directive-run/ai/anthropic';
308
+ import { createOpenAIRunner } from '@directive-run/ai/openai';
309
+ import { createOllamaRunner } from '@directive-run/ai/ollama';
310
+ import { createGeminiRunner } from '@directive-run/ai/gemini';
311
+ \`\`\`
312
+
313
+ ### Guardrails
314
+
315
+ \`createPIIGuardrail\`, \`createModerationGuardrail\`, \`createRateLimitGuardrail\`,
316
+ \`createToolGuardrail\`, \`createOutputSchemaGuardrail\`, \`createLengthGuardrail\`,
317
+ \`createContentFilterGuardrail\`
318
+
319
+ \`GuardrailResult: { passed: boolean, reason?: string, transformed?: unknown }\`
320
+
321
+ ### Memory
322
+
323
+ \`createAgentMemory({ strategy, summarizer?, autoManage? })\`
324
+ Strategies: \`createSlidingWindowStrategy\`, \`createTokenBasedStrategy\`, \`createHybridStrategy\`
325
+ Summarizers: \`createTruncationSummarizer\`, \`createKeyPointsSummarizer\`, \`createLLMSummarizer(runner)\`
326
+
327
+ ### Streaming
328
+
329
+ \`\`\`typescript
330
+ const streamResult = orchestrator.runStream(agent, "analyze");
331
+ for await (const chunk of streamResult.stream) {
332
+ switch (chunk.type) {
333
+ case "token": process.stdout.write(chunk.data); break;
334
+ case "done": console.log("Tokens:", chunk.totalTokens); break;
335
+ case "error": console.error(chunk.error); break;
336
+ }
337
+ }
338
+ \`\`\`
339
+
340
+ Backpressure: \`"buffer"\` (default), \`"block"\`, \`"drop"\`
341
+ `;
342
+ }
343
+
344
+ // src/templates/cursor.ts
345
+ function generateCursorRules() {
346
+ return `# Directive \u2014 AI Coding Rules
347
+
348
+ > Constraint-driven runtime for TypeScript. \`npm install @directive-run/core\`
349
+ > Full reference: https://directive.run/llms.txt
350
+
351
+ ## Schema Shape (CRITICAL)
352
+
353
+ \`\`\`typescript
354
+ import { createModule, createSystem, t } from "@directive-run/core";
355
+
356
+ const myModule = createModule("name", {
357
+ schema: {
358
+ facts: { count: t.number(), items: t.array<string>() },
359
+ derivations: { total: "number" },
360
+ events: { increment: "void", addItem: "string" },
361
+ requirements: { FETCH_DATA: { url: "string" } },
362
+ },
363
+ init: (facts) => { facts.count = 0; facts.items = []; },
364
+ derive: {
365
+ total: (facts) => facts.items.length + facts.count,
366
+ },
367
+ events: {
368
+ increment: (facts) => { facts.count += 1; },
369
+ addItem: (facts, item) => { facts.items = [...facts.items, item]; },
370
+ },
371
+ constraints: {
372
+ fetchWhenReady: {
373
+ when: (facts) => facts.count > 0 && facts.items.length === 0,
374
+ require: (facts) => ({ type: "FETCH_DATA", url: "/api/items" }),
375
+ },
376
+ },
377
+ resolvers: {
378
+ fetchData: {
379
+ requirement: "FETCH_DATA",
380
+ resolve: async (req, context) => {
381
+ const data = await fetch(req.url).then(r => r.json());
382
+ context.facts.items = data;
383
+ },
384
+ },
385
+ },
386
+ });
387
+
388
+ const system = createSystem({ module: myModule });
389
+ await system.settle();
390
+ \`\`\`
391
+
392
+ ## Top 10 Anti-Patterns
393
+
394
+ | # | WRONG | CORRECT |
395
+ |---|-------|---------|
396
+ | 1 | \`facts.profile as ResourceState<Profile>\` | Remove cast \u2014 schema provides types |
397
+ | 2 | \`{ phase: t.string() }\` flat schema | \`schema: { facts: { phase: t.string() } }\` |
398
+ | 3 | \`facts.items\` in multi-module | \`facts.self.items\` |
399
+ | 4 | \`t.map()\`, \`t.set()\`, \`t.promise()\` | Don't exist. Use \`t.object<Map<K,V>>()\` |
400
+ | 5 | \`(req, ctx)\` in resolver | \`(req, context)\` \u2014 never abbreviate |
401
+ | 6 | \`createModule("n", { phase: t.string() })\` | Must wrap: \`schema: { facts: { ... } }\` |
402
+ | 7 | \`system.dispatch('login', {...})\` | \`system.events.login({...})\` |
403
+ | 8 | \`facts.items.push(item)\` | \`facts.items = [...facts.items, item]\` |
404
+ | 9 | \`useDirective(system)\` | \`useSelector(system, s => s.facts.count)\` |
405
+ | 10 | \`facts['auth::status']\` | \`facts.auth.status\` dot notation |
406
+
407
+ ## Naming
408
+
409
+ - \`req\` = requirement (not request). Parameter: \`(req, context)\`
410
+ - \`derive\` / derivations \u2014 never "computed" or "selectors"
411
+ - Resolvers return \`void\` \u2014 mutate \`context.facts\` instead
412
+ - Always use braces for returns: \`if (x) { return y; }\`
413
+ - Multi-module: \`facts.self.fieldName\` for own module facts
414
+ - Events: \`system.events.eventName(payload)\` \u2014 not \`system.dispatch()\`
415
+ - Import from main: \`import { createModule } from '@directive-run/core'\`
416
+
417
+ ## Schema Types That Exist
418
+
419
+ \`t.string<T>()\`, \`t.number()\`, \`t.boolean()\`, \`t.array<T>()\`, \`t.object<T>()\`,
420
+ \`t.enum("a","b")\`, \`t.literal(value)\`, \`t.nullable(inner)\`, \`t.optional(inner)\`, \`t.union(...)\`
421
+
422
+ Chainable: \`.default()\`, \`.validate()\`, \`.transform()\`, \`.brand<>()\`, \`.refine()\`
423
+
424
+ **DO NOT USE** (hallucinations): \`t.map()\`, \`t.set()\`, \`t.date()\`, \`t.tuple()\`, \`t.record()\`, \`t.promise()\`, \`t.any()\`
425
+
426
+ ## Key Pattern: Constraint \u2192 Requirement \u2192 Resolver
427
+
428
+ When the user wants "do X when Y": create THREE things:
429
+ 1. **Constraint**: \`when: (facts) => Y_condition\` \u2192 \`require: { type: "DO_X" }\`
430
+ 2. **Resolver**: handles "DO_X", calls API, sets \`context.facts\`
431
+ 3. They are **decoupled**. Constraint declares need, resolver fulfills it.
432
+ `;
433
+ }
434
+
435
+ // src/templates/copilot.ts
436
+ function generateCopilotRules() {
437
+ const base = generateCursorRules();
438
+ const extraAntiPatterns = `
439
+ ## Anti-Patterns 11-20
440
+
441
+ | # | WRONG | CORRECT |
442
+ |---|-------|---------|
443
+ | 11 | \`module("name").schema({...}).build()\` | Prefer \`createModule("name", {...})\` object syntax |
444
+ | 12 | Returning data from \`resolve\` | Resolvers return \`void\` \u2014 mutate \`context.facts\` |
445
+ | 13 | Async logic in \`init\` | \`init\` is synchronous, facts assignment only |
446
+ | 14 | \`await system.start()\` without settle | Add \`await system.settle()\` after start |
447
+ | 15 | Missing \`crossModuleDeps\` | Declare \`crossModuleDeps: { auth: authSchema }\` |
448
+ | 16 | \`require: "TYPE"\` string literal | \`require: { type: "TYPE" }\` object form |
449
+ | 17 | Passthrough derivation \`(f) => f.count\` | Remove \u2014 read fact directly |
450
+ | 18 | \`from '@directive-run/core/module'\` | \`from '@directive-run/core'\` (main export) |
451
+ | 19 | \`async when()\` without \`deps\` | Add \`deps: ['factName']\` for async constraints |
452
+ | 20 | No error boundary on resolver | Use try-catch or module error boundary config |
453
+ `;
454
+ const multiModule = `
455
+ ## Multi-Module
456
+
457
+ \`\`\`typescript
458
+ const system = createSystem({
459
+ modules: { auth: authModule, cart: cartModule },
460
+ });
461
+
462
+ // Access: system.facts.auth.token, system.events.cart.addItem({...})
463
+ // In constraints/resolvers: use facts.self.* for own module
464
+ // Declare deps: crossModuleDeps: { auth: authSchema }
465
+ \`\`\`
466
+ `;
467
+ const aiBasics = `
468
+ ## AI Package Basics (\`@directive-run/ai\`)
469
+
470
+ \`\`\`typescript
471
+ import { createAgentOrchestrator, t } from '@directive-run/ai';
472
+ import { createAnthropicRunner } from '@directive-run/ai/anthropic'; // Subpath import!
473
+
474
+ const orchestrator = createAgentOrchestrator({
475
+ runner: createAnthropicRunner({ apiKey: process.env.ANTHROPIC_API_KEY }),
476
+ factsSchema: { result: t.string(), confidence: t.number() }, // Use t.*() !
477
+ init: (facts) => { facts.result = ""; facts.confidence = 0; },
478
+ });
479
+
480
+ const result = await orchestrator.run(agent, "analyze this");
481
+ \`\`\`
482
+
483
+ **AI Anti-Patterns:**
484
+ - Use \`t.number()\` not \`number\` for factsSchema
485
+ - Subpath imports: \`from '@directive-run/ai/anthropic'\` not \`from '@directive-run/ai'\`
486
+ - Token usage normalized: \`{ inputTokens, outputTokens }\` (not provider-specific)
487
+ - \`facts.cache = [...facts.cache, item]\` not \`facts.cache.push(item)\`
488
+ `;
489
+ return base + extraAntiPatterns + multiModule + aiBasics;
490
+ }
491
+
492
+ // src/templates/cline.ts
493
+ function generateClineRules() {
494
+ return generateCopilotRules();
495
+ }
496
+
497
+ // src/templates/windsurf.ts
498
+ function generateWindsurfRules() {
499
+ return generateCopilotRules();
500
+ }
501
+
502
+ // src/templates/index.ts
503
+ var generators = {
504
+ cursor: generateCursorRules,
505
+ claude: generateClaudeRules,
506
+ copilot: generateCopilotRules,
507
+ windsurf: generateWindsurfRules,
508
+ cline: generateClineRules
509
+ };
510
+ function getTemplate(toolId) {
511
+ const generator = generators[toolId];
512
+ if (!generator) {
513
+ throw new Error(`No template for tool: ${toolId}`);
514
+ }
515
+ return generator();
516
+ }
517
+
518
+ // src/commands/ai-rules.ts
519
+ function parseArgs(args) {
520
+ const opts = {
521
+ force: false,
522
+ merge: false,
523
+ tools: [],
524
+ dir: process.cwd()
525
+ };
526
+ for (let i = 0; i < args.length; i++) {
527
+ const arg = args[i];
528
+ switch (arg) {
529
+ case "--force":
530
+ opts.force = true;
531
+ break;
532
+ case "--merge":
533
+ opts.merge = true;
534
+ break;
535
+ case "--tool": {
536
+ const val = args[++i];
537
+ if (val) {
538
+ opts.tools.push(val);
539
+ }
540
+ break;
541
+ }
542
+ case "--dir": {
543
+ const val = args[++i];
544
+ if (val) {
545
+ opts.dir = val;
546
+ }
547
+ break;
548
+ }
549
+ }
550
+ }
551
+ return opts;
552
+ }
553
+ async function aiRulesCommand(args) {
554
+ const opts = parseArgs(args);
555
+ p.intro(pc5.bgCyan(pc5.black(" directive ai-rules ")));
556
+ const mono = detectMonorepo(opts.dir);
557
+ let targetDir = opts.dir;
558
+ if (mono.isMonorepo && mono.rootDir !== opts.dir) {
559
+ const placement = await p.select({
560
+ message: "Monorepo detected. Where should AI rules be installed?",
561
+ options: [
562
+ {
563
+ value: "root",
564
+ label: `Monorepo root (${relative(opts.dir, mono.rootDir) || "."})`,
565
+ hint: "recommended"
566
+ },
567
+ {
568
+ value: "workspace",
569
+ label: `Current workspace (${relative(mono.rootDir, opts.dir)})`
570
+ }
571
+ ]
572
+ });
573
+ if (p.isCancel(placement)) {
574
+ p.cancel("Cancelled.");
575
+ process.exit(0);
576
+ }
577
+ if (placement === "root") {
578
+ targetDir = mono.rootDir;
579
+ }
580
+ }
581
+ let selectedTools;
582
+ if (opts.tools.length > 0) {
583
+ selectedTools = opts.tools.map((id) => {
584
+ const config = getToolConfig(id);
585
+ return {
586
+ name: config.name,
587
+ id: config.id,
588
+ outputPath: join(targetDir, config.outputPath)
589
+ };
590
+ });
591
+ } else {
592
+ const detected = detectTools(targetDir);
593
+ if (detected.length > 0) {
594
+ const choices = await p.multiselect({
595
+ message: `Detected ${detected.length} AI tool(s). Which should get Directive rules?`,
596
+ options: detected.map((t) => ({
597
+ value: t.id,
598
+ label: t.name,
599
+ hint: relative(targetDir, t.outputPath)
600
+ })),
601
+ initialValues: detected.map((t) => t.id),
602
+ required: true
603
+ });
604
+ if (p.isCancel(choices)) {
605
+ p.cancel("Cancelled.");
606
+ process.exit(0);
607
+ }
608
+ selectedTools = choices.map((id) => {
609
+ const config = getToolConfig(id);
610
+ return {
611
+ name: config.name,
612
+ id: config.id,
613
+ outputPath: join(targetDir, config.outputPath)
614
+ };
615
+ });
616
+ } else {
617
+ const choices = await p.multiselect({
618
+ message: "No AI tools detected. Which tools do you use?",
619
+ options: getAllToolIds().map((id) => {
620
+ const config = getToolConfig(id);
621
+ return { value: id, label: config.name };
622
+ }),
623
+ required: true
624
+ });
625
+ if (p.isCancel(choices)) {
626
+ p.cancel("Cancelled.");
627
+ process.exit(0);
628
+ }
629
+ selectedTools = choices.map((id) => {
630
+ const config = getToolConfig(id);
631
+ return {
632
+ name: config.name,
633
+ id: config.id,
634
+ outputPath: join(targetDir, config.outputPath)
635
+ };
636
+ });
637
+ }
638
+ }
639
+ if (selectedTools.length === 0) {
640
+ p.cancel("No tools selected.");
641
+ process.exit(0);
642
+ }
643
+ const s = p.spinner();
644
+ for (const tool of selectedTools) {
645
+ s.start(`Generating ${tool.name} rules...`);
646
+ const content = getTemplate(tool.id);
647
+ const filePath = tool.outputPath;
648
+ const fileExists = existsSync(filePath);
649
+ s.stop(`Generated ${tool.name} rules.`);
650
+ if (fileExists && !opts.force) {
651
+ const existingContent = readFileSync(filePath, "utf-8");
652
+ if (opts.merge) {
653
+ writeFile(filePath, mergeSection(existingContent, content));
654
+ p.log.success(
655
+ `${pc5.green("Merged")} Directive section into ${pc5.dim(relative(targetDir, filePath))}`
656
+ );
657
+ continue;
658
+ }
659
+ if (hasDirectiveSection(existingContent)) {
660
+ const action = await p.select({
661
+ message: `${relative(targetDir, filePath)} already has a Directive section. What should we do?`,
662
+ options: [
663
+ {
664
+ value: "merge",
665
+ label: "Update Directive section only",
666
+ hint: "recommended"
667
+ },
668
+ { value: "overwrite", label: "Overwrite entire file" },
669
+ { value: "skip", label: "Skip this file" }
670
+ ]
671
+ });
672
+ if (p.isCancel(action)) {
673
+ p.cancel("Cancelled.");
674
+ process.exit(0);
675
+ }
676
+ if (action === "merge") {
677
+ writeFile(filePath, mergeSection(existingContent, content));
678
+ p.log.success(
679
+ `${pc5.green("Updated")} ${pc5.dim(relative(targetDir, filePath))}`
680
+ );
681
+ } else if (action === "overwrite") {
682
+ writeFile(filePath, content);
683
+ p.log.success(
684
+ `${pc5.green("Wrote")} ${pc5.dim(relative(targetDir, filePath))}`
685
+ );
686
+ } else {
687
+ p.log.info(`Skipped ${pc5.dim(relative(targetDir, filePath))}`);
688
+ }
689
+ } else {
690
+ const action = await p.select({
691
+ message: `${relative(targetDir, filePath)} already exists. What should we do?`,
692
+ options: [
693
+ {
694
+ value: "append",
695
+ label: "Append Directive section",
696
+ hint: "preserves existing content"
697
+ },
698
+ { value: "overwrite", label: "Overwrite entire file" },
699
+ { value: "skip", label: "Skip this file" }
700
+ ]
701
+ });
702
+ if (p.isCancel(action)) {
703
+ p.cancel("Cancelled.");
704
+ process.exit(0);
705
+ }
706
+ if (action === "append") {
707
+ writeFile(filePath, mergeSection(existingContent, content));
708
+ p.log.success(
709
+ `${pc5.green("Appended")} to ${pc5.dim(relative(targetDir, filePath))}`
710
+ );
711
+ } else if (action === "overwrite") {
712
+ writeFile(filePath, content);
713
+ p.log.success(
714
+ `${pc5.green("Wrote")} ${pc5.dim(relative(targetDir, filePath))}`
715
+ );
716
+ } else {
717
+ p.log.info(`Skipped ${pc5.dim(relative(targetDir, filePath))}`);
718
+ }
719
+ }
720
+ } else {
721
+ writeFile(filePath, content);
722
+ p.log.success(
723
+ `${pc5.green("Created")} ${pc5.dim(relative(targetDir, filePath))}`
724
+ );
725
+ }
726
+ }
727
+ p.outro(
728
+ `Done! Run ${pc5.cyan(`${CLI_NAME} ai-rules init --merge`)} anytime to update.`
729
+ );
730
+ }
731
+ function writeFile(filePath, content) {
732
+ const dir = dirname(filePath);
733
+ if (!existsSync(dir)) {
734
+ mkdirSync(dir, { recursive: true });
735
+ }
736
+ writeFileSync(filePath, content, "utf-8");
737
+ }
738
+ async function aiRulesUpdateCommand(args) {
739
+ const opts = parseArgs(args);
740
+ const targetDir = opts.dir;
741
+ const ruleFiles = [
742
+ { id: "cursor", path: join(targetDir, ".cursorrules") },
743
+ { id: "claude", path: join(targetDir, ".claude/CLAUDE.md") },
744
+ { id: "copilot", path: join(targetDir, ".github/copilot-instructions.md") },
745
+ { id: "windsurf", path: join(targetDir, ".windsurfrules") },
746
+ { id: "cline", path: join(targetDir, ".clinerules") }
747
+ ];
748
+ let updated = 0;
749
+ for (const file of ruleFiles) {
750
+ if (!existsSync(file.path)) {
751
+ continue;
752
+ }
753
+ const existing = readFileSync(file.path, "utf-8");
754
+ if (!hasDirectiveSection(existing)) {
755
+ continue;
756
+ }
757
+ const newContent = getTemplate(file.id);
758
+ const merged = mergeSection(existing, newContent);
759
+ writeFile(file.path, merged);
760
+ console.log(
761
+ `${pc5.green("Updated")} ${pc5.dim(relative(targetDir, file.path))}`
762
+ );
763
+ updated++;
764
+ }
765
+ if (updated === 0) {
766
+ console.log(
767
+ pc5.dim(
768
+ `No existing rule files found. Run ${pc5.cyan(`${CLI_NAME} ai-rules init`)} first.`
769
+ )
770
+ );
771
+ } else {
772
+ console.log(
773
+ pc5.green(`
774
+ Updated ${updated} file(s) to latest knowledge version.`)
775
+ );
776
+ }
777
+ }
778
+ async function aiRulesCheckCommand(args) {
779
+ const opts = parseArgs(args);
780
+ const targetDir = opts.dir;
781
+ const ruleFiles = [
782
+ { id: "cursor", path: join(targetDir, ".cursorrules"), name: "Cursor" },
783
+ { id: "claude", path: join(targetDir, ".claude/CLAUDE.md"), name: "Claude Code" },
784
+ { id: "copilot", path: join(targetDir, ".github/copilot-instructions.md"), name: "GitHub Copilot" },
785
+ { id: "windsurf", path: join(targetDir, ".windsurfrules"), name: "Windsurf" },
786
+ { id: "cline", path: join(targetDir, ".clinerules"), name: "Cline" }
787
+ ];
788
+ let checked = 0;
789
+ let stale = 0;
790
+ for (const file of ruleFiles) {
791
+ if (!existsSync(file.path)) {
792
+ continue;
793
+ }
794
+ const existing = readFileSync(file.path, "utf-8");
795
+ if (!hasDirectiveSection(existing)) {
796
+ continue;
797
+ }
798
+ checked++;
799
+ const freshContent = getTemplate(file.id);
800
+ const merged = mergeSection(existing, freshContent);
801
+ if (merged !== existing) {
802
+ console.log(`${pc5.red("\u2717")} ${file.name} rules are ${pc5.yellow("stale")}`);
803
+ stale++;
804
+ } else {
805
+ console.log(`${pc5.green("\u2713")} ${file.name} rules are ${pc5.green("current")}`);
806
+ }
807
+ }
808
+ if (checked === 0) {
809
+ console.log(pc5.dim("No rule files found to check."));
810
+ return;
811
+ }
812
+ if (stale > 0) {
813
+ console.log(
814
+ `
815
+ ${pc5.yellow(`${stale} file(s) are stale.`)} Run ${pc5.cyan(`${CLI_NAME} ai-rules update`)} to refresh.`
816
+ );
817
+ process.exit(1);
818
+ } else {
819
+ console.log(pc5.green("\nAll rule files are current."));
820
+ }
821
+ }
822
+ function parseArgs2(args) {
823
+ const opts = {
824
+ dir: process.cwd(),
825
+ noInteractive: false
826
+ };
827
+ for (let i = 0; i < args.length; i++) {
828
+ const arg = args[i];
829
+ switch (arg) {
830
+ case "--template": {
831
+ const val = args[++i];
832
+ if (val) {
833
+ opts.template = val;
834
+ }
835
+ break;
836
+ }
837
+ case "--dir": {
838
+ const val = args[++i];
839
+ if (val) {
840
+ opts.dir = val;
841
+ }
842
+ break;
843
+ }
844
+ case "--no-interactive":
845
+ opts.noInteractive = true;
846
+ break;
847
+ }
848
+ }
849
+ return opts;
850
+ }
851
+ function detectPackageManager(dir) {
852
+ if (existsSync(join(dir, "pnpm-lock.yaml"))) {
853
+ return "pnpm";
854
+ }
855
+ if (existsSync(join(dir, "bun.lockb")) || existsSync(join(dir, "bun.lock"))) {
856
+ return "bun";
857
+ }
858
+ if (existsSync(join(dir, "yarn.lock"))) {
859
+ return "yarn";
860
+ }
861
+ return "npm";
862
+ }
863
+ function installCmd(pm, pkg) {
864
+ switch (pm) {
865
+ case "pnpm":
866
+ return `pnpm add ${pkg}`;
867
+ case "yarn":
868
+ return `yarn add ${pkg}`;
869
+ case "bun":
870
+ return `bun add ${pkg}`;
871
+ default:
872
+ return `npm install ${pkg}`;
873
+ }
874
+ }
875
+ function getTemplates(moduleName) {
876
+ return {
877
+ counter: {
878
+ id: "counter",
879
+ label: "Counter (minimal)",
880
+ hint: "schema + init + derive + events \u2014 simplest starting point",
881
+ files: [
882
+ {
883
+ path: `src/${moduleName}.ts`,
884
+ content: generateCounterModule(moduleName)
885
+ },
886
+ {
887
+ path: "src/main.ts",
888
+ content: generateCounterMain(moduleName)
889
+ }
890
+ ],
891
+ deps: ["@directive-run/core"]
892
+ },
893
+ "auth-flow": {
894
+ id: "auth-flow",
895
+ label: "Auth flow (constraints + resolvers)",
896
+ hint: "login flow with constraints, resolvers, retry, and effects",
897
+ files: [
898
+ {
899
+ path: `src/${moduleName}.ts`,
900
+ content: generateAuthModule(moduleName)
901
+ },
902
+ {
903
+ path: "src/main.ts",
904
+ content: generateAuthMain(moduleName)
905
+ }
906
+ ],
907
+ deps: ["@directive-run/core"]
908
+ },
909
+ "ai-orchestrator": {
910
+ id: "ai-orchestrator",
911
+ label: "AI orchestrator",
912
+ hint: "agent orchestrator with guardrails and streaming",
913
+ files: [
914
+ {
915
+ path: `src/${moduleName}.ts`,
916
+ content: generateAIModule(moduleName)
917
+ },
918
+ {
919
+ path: "src/main.ts",
920
+ content: generateAIMain(moduleName)
921
+ }
922
+ ],
923
+ deps: ["@directive-run/core", "@directive-run/ai"]
924
+ }
925
+ };
926
+ }
927
+ function generateCounterModule(name) {
928
+ const camelName = toCamelCase(name);
929
+ return `import { type ModuleSchema, createModule, t } from "@directive-run/core";
930
+
931
+ const schema = {
932
+ facts: {
933
+ count: t.number(),
934
+ label: t.string(),
935
+ },
936
+ derivations: {
937
+ isEven: t.boolean(),
938
+ display: t.string(),
939
+ },
940
+ events: {
941
+ increment: {},
942
+ decrement: {},
943
+ reset: {},
944
+ setLabel: { value: t.string() },
945
+ },
946
+ } satisfies ModuleSchema;
947
+
948
+ export const ${camelName} = createModule("${name}", {
949
+ schema,
950
+
951
+ init: (facts) => {
952
+ facts.count = 0;
953
+ facts.label = "${name}";
954
+ },
955
+
956
+ derive: {
957
+ isEven: (facts) => facts.count % 2 === 0,
958
+ display: (facts) => \`\${facts.label}: \${facts.count}\`,
959
+ },
960
+
961
+ events: {
962
+ increment: (facts) => {
963
+ facts.count++;
964
+ },
965
+ decrement: (facts) => {
966
+ facts.count--;
967
+ },
968
+ reset: (facts) => {
969
+ facts.count = 0;
970
+ },
971
+ setLabel: (facts, { value }) => {
972
+ facts.label = value;
973
+ },
974
+ },
975
+ });
976
+ `;
977
+ }
978
+ function generateCounterMain(name) {
979
+ const camelName = toCamelCase(name);
980
+ return `import { createSystem } from "@directive-run/core";
981
+ import { ${camelName} } from "./${name}.js";
982
+
983
+ const system = createSystem({
984
+ module: ${camelName},
985
+ });
986
+
987
+ system.start();
988
+
989
+ // Read facts and derivations
990
+ console.log("count:", system.facts.count);
991
+ console.log("display:", system.read("display"));
992
+
993
+ // Dispatch events
994
+ system.events.increment();
995
+ console.log("after increment:", system.facts.count);
996
+
997
+ // Subscribe to changes
998
+ system.subscribe(["count"], () => {
999
+ console.log("count changed:", system.facts.count);
1000
+ });
1001
+
1002
+ system.events.increment();
1003
+ system.events.increment();
1004
+
1005
+ export default system;
1006
+ `;
1007
+ }
1008
+ function generateAuthModule(name) {
1009
+ const camelName = toCamelCase(name);
1010
+ return `import { type ModuleSchema, createModule, t } from "@directive-run/core";
1011
+
1012
+ type AuthStatus = "idle" | "authenticating" | "authenticated" | "expired";
1013
+
1014
+ const schema = {
1015
+ facts: {
1016
+ email: t.string(),
1017
+ password: t.string(),
1018
+ token: t.string(),
1019
+ status: t.string<AuthStatus>(),
1020
+ error: t.string(),
1021
+ },
1022
+ derivations: {
1023
+ isAuthenticated: t.boolean(),
1024
+ canLogin: t.boolean(),
1025
+ },
1026
+ events: {
1027
+ setEmail: { value: t.string() },
1028
+ setPassword: { value: t.string() },
1029
+ requestLogin: {},
1030
+ logout: {},
1031
+ },
1032
+ requirements: {
1033
+ LOGIN: { email: t.string(), password: t.string() },
1034
+ },
1035
+ } satisfies ModuleSchema;
1036
+
1037
+ export const ${camelName} = createModule("${name}", {
1038
+ schema,
1039
+
1040
+ init: (facts) => {
1041
+ facts.email = "";
1042
+ facts.password = "";
1043
+ facts.token = "";
1044
+ facts.status = "idle";
1045
+ facts.error = "";
1046
+ },
1047
+
1048
+ derive: {
1049
+ isAuthenticated: (facts) => facts.status === "authenticated",
1050
+ canLogin: (facts) => {
1051
+ return (
1052
+ facts.email.trim() !== "" &&
1053
+ facts.password.trim() !== "" &&
1054
+ (facts.status === "idle" || facts.status === "expired")
1055
+ );
1056
+ },
1057
+ },
1058
+
1059
+ events: {
1060
+ setEmail: (facts, { value }) => {
1061
+ facts.email = value;
1062
+ },
1063
+ setPassword: (facts, { value }) => {
1064
+ facts.password = value;
1065
+ },
1066
+ requestLogin: (facts) => {
1067
+ facts.status = "authenticating";
1068
+ facts.error = "";
1069
+ },
1070
+ logout: (facts) => {
1071
+ facts.token = "";
1072
+ facts.status = "idle";
1073
+ },
1074
+ },
1075
+
1076
+ constraints: {
1077
+ needsLogin: {
1078
+ priority: 100,
1079
+ when: (facts) => facts.status === "authenticating",
1080
+ require: (facts) => ({
1081
+ type: "LOGIN",
1082
+ email: facts.email,
1083
+ password: facts.password,
1084
+ }),
1085
+ },
1086
+ },
1087
+
1088
+ resolvers: {
1089
+ login: {
1090
+ requirement: "LOGIN",
1091
+ retry: { attempts: 2, backoff: "exponential" },
1092
+ resolve: async (req, context) => {
1093
+ // Replace with real auth API call
1094
+ await new Promise((resolve) => setTimeout(resolve, 500));
1095
+
1096
+ const token = \`token_\${Date.now()}\`;
1097
+ context.facts.token = token;
1098
+ context.facts.status = "authenticated";
1099
+ },
1100
+ },
1101
+ },
1102
+
1103
+ effects: {
1104
+ logStatusChange: {
1105
+ deps: ["status"],
1106
+ run: (facts, prev) => {
1107
+ if (prev && prev.status !== facts.status) {
1108
+ console.log(\`Auth status: \${prev.status} \u2192 \${facts.status}\`);
1109
+ }
1110
+ },
1111
+ },
1112
+ },
1113
+ });
1114
+ `;
1115
+ }
1116
+ function generateAuthMain(name) {
1117
+ const camelName = toCamelCase(name);
1118
+ return `import { createSystem } from "@directive-run/core";
1119
+ import { ${camelName} } from "./${name}.js";
1120
+
1121
+ const system = createSystem({
1122
+ module: ${camelName},
1123
+ });
1124
+
1125
+ system.start();
1126
+
1127
+ // Set credentials and login
1128
+ system.events.setEmail({ value: "user@example.com" });
1129
+ system.events.setPassword({ value: "password123" });
1130
+ system.events.requestLogin();
1131
+
1132
+ // Wait for auth to complete
1133
+ await system.settle();
1134
+
1135
+ console.log("authenticated:", system.read("isAuthenticated"));
1136
+ console.log("token:", system.facts.token);
1137
+
1138
+ export default system;
1139
+ `;
1140
+ }
1141
+ function generateAIModule(name) {
1142
+ const camelName = toCamelCase(name);
1143
+ return `import { type ModuleSchema, createModule, t } from "@directive-run/core";
1144
+ import {
1145
+ createAgentOrchestrator,
1146
+ createAgentMemory,
1147
+ createSlidingWindowStrategy,
1148
+ } from "@directive-run/ai";
1149
+
1150
+ // ============================================================================
1151
+ // Module \u2014 state management
1152
+ // ============================================================================
1153
+
1154
+ type AgentStatus = "idle" | "thinking" | "done" | "error";
1155
+
1156
+ const schema = {
1157
+ facts: {
1158
+ input: t.string(),
1159
+ output: t.string(),
1160
+ status: t.string<AgentStatus>(),
1161
+ error: t.string(),
1162
+ },
1163
+ derivations: {
1164
+ isThinking: t.boolean(),
1165
+ hasOutput: t.boolean(),
1166
+ },
1167
+ events: {
1168
+ setInput: { value: t.string() },
1169
+ requestRun: {},
1170
+ reset: {},
1171
+ },
1172
+ requirements: {
1173
+ RUN_AGENT: { input: t.string() },
1174
+ },
1175
+ } satisfies ModuleSchema;
1176
+
1177
+ export const ${camelName} = createModule("${name}", {
1178
+ schema,
1179
+
1180
+ init: (facts) => {
1181
+ facts.input = "";
1182
+ facts.output = "";
1183
+ facts.status = "idle";
1184
+ facts.error = "";
1185
+ },
1186
+
1187
+ derive: {
1188
+ isThinking: (facts) => facts.status === "thinking",
1189
+ hasOutput: (facts) => facts.output !== "",
1190
+ },
1191
+
1192
+ events: {
1193
+ setInput: (facts, { value }) => {
1194
+ facts.input = value;
1195
+ },
1196
+ requestRun: (facts) => {
1197
+ facts.status = "thinking";
1198
+ facts.output = "";
1199
+ facts.error = "";
1200
+ },
1201
+ reset: (facts) => {
1202
+ facts.input = "";
1203
+ facts.output = "";
1204
+ facts.status = "idle";
1205
+ facts.error = "";
1206
+ },
1207
+ },
1208
+
1209
+ constraints: {
1210
+ needsRun: {
1211
+ priority: 100,
1212
+ when: (facts) => facts.status === "thinking",
1213
+ require: (facts) => ({
1214
+ type: "RUN_AGENT",
1215
+ input: facts.input,
1216
+ }),
1217
+ },
1218
+ },
1219
+
1220
+ resolvers: {
1221
+ runAgent: {
1222
+ requirement: "RUN_AGENT",
1223
+ timeout: 30000,
1224
+ resolve: async (req, context) => {
1225
+ // Replace with your agent runner (e.g., Anthropic, OpenAI)
1226
+ const result = \`Echo: \${req.input}\`;
1227
+
1228
+ context.facts.output = result;
1229
+ context.facts.status = "done";
1230
+ },
1231
+ },
1232
+ },
1233
+ });
1234
+
1235
+ // ============================================================================
1236
+ // Orchestrator \u2014 optional AI features
1237
+ // ============================================================================
1238
+
1239
+ export const memory = createAgentMemory({
1240
+ strategy: createSlidingWindowStrategy(),
1241
+ strategyConfig: { maxMessages: 30, preserveRecentCount: 6 },
1242
+ autoManage: true,
1243
+ });
1244
+
1245
+ // Uncomment to add orchestrator features:
1246
+ // export const orchestrator = createAgentOrchestrator({
1247
+ // runner: yourAgentRunner,
1248
+ // maxTokenBudget: 50000,
1249
+ // memory,
1250
+ // });
1251
+ `;
1252
+ }
1253
+ function generateAIMain(name) {
1254
+ const camelName = toCamelCase(name);
1255
+ return `import { createSystem } from "@directive-run/core";
1256
+ import { ${camelName} } from "./${name}.js";
1257
+
1258
+ const system = createSystem({
1259
+ module: ${camelName},
1260
+ });
1261
+
1262
+ system.start();
1263
+
1264
+ // Set input and run
1265
+ system.events.setInput({ value: "Hello, world!" });
1266
+ system.events.requestRun();
1267
+
1268
+ // Wait for completion
1269
+ await system.settle();
1270
+
1271
+ console.log("output:", system.facts.output);
1272
+
1273
+ export default system;
1274
+ `;
1275
+ }
1276
+ function toCamelCase(name) {
1277
+ return name.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
1278
+ }
1279
+ function writeFile2(filePath, content) {
1280
+ const dir = dirname(filePath);
1281
+ if (!existsSync(dir)) {
1282
+ mkdirSync(dir, { recursive: true });
1283
+ }
1284
+ writeFileSync(filePath, content, "utf-8");
1285
+ }
1286
+ async function initCommand(args) {
1287
+ const opts = parseArgs2(args);
1288
+ p.intro(pc5.bgCyan(pc5.black(" directive init ")));
1289
+ let moduleName;
1290
+ if (opts.noInteractive) {
1291
+ moduleName = "my-module";
1292
+ } else {
1293
+ const nameResult = await p.text({
1294
+ message: "Module name:",
1295
+ placeholder: "my-module",
1296
+ defaultValue: "my-module",
1297
+ validate: (val) => {
1298
+ if (!/^[a-z][a-z0-9-]*$/.test(val)) {
1299
+ return "Must start with a letter, use lowercase letters, numbers, and hyphens";
1300
+ }
1301
+ }
1302
+ });
1303
+ if (p.isCancel(nameResult)) {
1304
+ p.cancel("Cancelled.");
1305
+ process.exit(0);
1306
+ }
1307
+ moduleName = nameResult;
1308
+ }
1309
+ let templateId;
1310
+ if (opts.template) {
1311
+ const templates2 = getTemplates(moduleName);
1312
+ if (!(opts.template in templates2)) {
1313
+ p.log.error(
1314
+ `Unknown template: ${opts.template}. Available: ${Object.keys(templates2).join(", ")}`
1315
+ );
1316
+ process.exit(1);
1317
+ }
1318
+ templateId = opts.template;
1319
+ } else if (opts.noInteractive) {
1320
+ templateId = "counter";
1321
+ } else {
1322
+ const templates2 = getTemplates(moduleName);
1323
+ const choice = await p.select({
1324
+ message: "Project template:",
1325
+ options: Object.values(templates2).map((t) => ({
1326
+ value: t.id,
1327
+ label: t.label,
1328
+ hint: t.hint
1329
+ }))
1330
+ });
1331
+ if (p.isCancel(choice)) {
1332
+ p.cancel("Cancelled.");
1333
+ process.exit(0);
1334
+ }
1335
+ templateId = choice;
1336
+ }
1337
+ const templates = getTemplates(moduleName);
1338
+ const template = templates[templateId];
1339
+ const pm = detectPackageManager(opts.dir);
1340
+ p.log.info(`Package manager: ${pc5.cyan(pm)}`);
1341
+ const s = p.spinner();
1342
+ s.start("Creating project files...");
1343
+ let skipped = 0;
1344
+ for (const file of template.files) {
1345
+ const filePath = join(opts.dir, file.path);
1346
+ if (existsSync(filePath)) {
1347
+ skipped++;
1348
+ continue;
1349
+ }
1350
+ writeFile2(filePath, file.content);
1351
+ }
1352
+ s.stop("Project files created.");
1353
+ for (const file of template.files) {
1354
+ const filePath = join(opts.dir, file.path);
1355
+ const rel = relative(opts.dir, filePath);
1356
+ if (existsSync(filePath)) {
1357
+ p.log.success(`${pc5.green("Created")} ${pc5.dim(rel)}`);
1358
+ }
1359
+ }
1360
+ if (skipped > 0) {
1361
+ p.log.warn(`Skipped ${skipped} file(s) that already exist.`);
1362
+ }
1363
+ const depsCmd = installCmd(pm, template.deps.join(" "));
1364
+ p.outro(
1365
+ `Next steps:
1366
+ ${pc5.cyan(depsCmd)}
1367
+ ${pc5.cyan(`${CLI_NAME} ai-rules init`)}
1368
+ ${pc5.dim("Start building!")}`
1369
+ );
1370
+ }
1371
+ function parseArgs3(args) {
1372
+ const opts = {
1373
+ with: [],
1374
+ minimal: false,
1375
+ dir: process.cwd()
1376
+ };
1377
+ for (let i = 0; i < args.length; i++) {
1378
+ const arg = args[i];
1379
+ switch (arg) {
1380
+ case "--with": {
1381
+ const val = args[++i];
1382
+ if (val) {
1383
+ opts.with = val.split(",").map((s) => s.trim());
1384
+ }
1385
+ break;
1386
+ }
1387
+ case "--minimal":
1388
+ opts.minimal = true;
1389
+ break;
1390
+ case "--dir": {
1391
+ const val = args[++i];
1392
+ if (val) {
1393
+ opts.dir = val;
1394
+ }
1395
+ break;
1396
+ }
1397
+ }
1398
+ }
1399
+ return opts;
1400
+ }
1401
+ var ALL_SECTIONS = [
1402
+ "derive",
1403
+ "events",
1404
+ "constraints",
1405
+ "resolvers",
1406
+ "effects"
1407
+ ];
1408
+ function generateModule(name, sections) {
1409
+ const camelName = toCamelCase2(name);
1410
+ const hasConstraints = sections.includes("constraints");
1411
+ const hasResolvers = sections.includes("resolvers");
1412
+ const imports = ["type ModuleSchema", "createModule", "t"];
1413
+ let code = `import { ${imports.join(", ")} } from "@directive-run/core";
1414
+
1415
+ `;
1416
+ code += `const schema = {
1417
+ `;
1418
+ code += ` facts: {
1419
+ `;
1420
+ code += ` // Add your facts here
1421
+ `;
1422
+ code += ` status: t.string(),
1423
+ `;
1424
+ code += ` },
1425
+ `;
1426
+ if (sections.includes("derive")) {
1427
+ code += ` derivations: {
1428
+ `;
1429
+ code += ` // Add derivation types here
1430
+ `;
1431
+ code += ` isReady: t.boolean(),
1432
+ `;
1433
+ code += ` },
1434
+ `;
1435
+ }
1436
+ if (sections.includes("events")) {
1437
+ code += ` events: {
1438
+ `;
1439
+ code += ` // Add event shapes here
1440
+ `;
1441
+ code += ` setStatus: { value: t.string() },
1442
+ `;
1443
+ code += ` },
1444
+ `;
1445
+ }
1446
+ if (hasConstraints || hasResolvers) {
1447
+ code += ` requirements: {
1448
+ `;
1449
+ code += ` // Add requirement shapes here
1450
+ `;
1451
+ code += ` PROCESS: { input: t.string() },
1452
+ `;
1453
+ code += ` },
1454
+ `;
1455
+ }
1456
+ code += `} satisfies ModuleSchema;
1457
+
1458
+ `;
1459
+ code += `export const ${camelName} = createModule("${name}", {
1460
+ `;
1461
+ code += ` schema,
1462
+
1463
+ `;
1464
+ code += ` init: (facts) => {
1465
+ `;
1466
+ code += ` facts.status = "idle";
1467
+ `;
1468
+ code += ` },
1469
+ `;
1470
+ if (sections.includes("derive")) {
1471
+ code += `
1472
+ derive: {
1473
+ `;
1474
+ code += ` isReady: (facts) => facts.status === "ready",
1475
+ `;
1476
+ code += ` },
1477
+ `;
1478
+ }
1479
+ if (sections.includes("events")) {
1480
+ code += `
1481
+ events: {
1482
+ `;
1483
+ code += ` setStatus: (facts, { value }) => {
1484
+ `;
1485
+ code += ` facts.status = value;
1486
+ `;
1487
+ code += ` },
1488
+ `;
1489
+ code += ` },
1490
+ `;
1491
+ }
1492
+ if (hasConstraints) {
1493
+ code += `
1494
+ constraints: {
1495
+ `;
1496
+ code += ` needsProcessing: {
1497
+ `;
1498
+ code += ` priority: 100,
1499
+ `;
1500
+ code += ` when: (facts) => facts.status === "pending",
1501
+ `;
1502
+ code += ` require: (facts) => ({
1503
+ `;
1504
+ code += ` type: "PROCESS",
1505
+ `;
1506
+ code += ` input: facts.status,
1507
+ `;
1508
+ code += ` }),
1509
+ `;
1510
+ code += ` },
1511
+ `;
1512
+ code += ` },
1513
+ `;
1514
+ }
1515
+ if (hasResolvers) {
1516
+ code += `
1517
+ resolvers: {
1518
+ `;
1519
+ code += ` process: {
1520
+ `;
1521
+ code += ` requirement: "PROCESS",
1522
+ `;
1523
+ code += ` resolve: async (req, context) => {
1524
+ `;
1525
+ code += ` // Implement resolution logic here
1526
+ `;
1527
+ code += ` context.facts.status = "done";
1528
+ `;
1529
+ code += ` },
1530
+ `;
1531
+ code += ` },
1532
+ `;
1533
+ code += ` },
1534
+ `;
1535
+ }
1536
+ if (sections.includes("effects")) {
1537
+ code += `
1538
+ effects: {
1539
+ `;
1540
+ code += ` logChange: {
1541
+ `;
1542
+ code += ` deps: ["status"],
1543
+ `;
1544
+ code += ` run: (facts, prev) => {
1545
+ `;
1546
+ code += ` if (prev && prev.status !== facts.status) {
1547
+ `;
1548
+ code += ` console.log(\`Status: \${prev.status} \u2192 \${facts.status}\`);
1549
+ `;
1550
+ code += ` }
1551
+ `;
1552
+ code += ` },
1553
+ `;
1554
+ code += ` },
1555
+ `;
1556
+ code += ` },
1557
+ `;
1558
+ }
1559
+ code += `});
1560
+ `;
1561
+ return code;
1562
+ }
1563
+ function generateOrchestrator(name) {
1564
+ const camelName = toCamelCase2(name);
1565
+ return `import { type ModuleSchema, createModule, createSystem, t } from "@directive-run/core";
1566
+ import {
1567
+ createAgentOrchestrator,
1568
+ createAgentMemory,
1569
+ createSlidingWindowStrategy,
1570
+ } from "@directive-run/ai";
1571
+
1572
+ // ============================================================================
1573
+ // Types
1574
+ // ============================================================================
1575
+
1576
+ type AgentStatus = "idle" | "thinking" | "done" | "error";
1577
+
1578
+ // ============================================================================
1579
+ // Schema
1580
+ // ============================================================================
1581
+
1582
+ const schema = {
1583
+ facts: {
1584
+ input: t.string(),
1585
+ output: t.string(),
1586
+ status: t.string<AgentStatus>(),
1587
+ error: t.string(),
1588
+ totalTokens: t.number(),
1589
+ },
1590
+ derivations: {
1591
+ isThinking: t.boolean(),
1592
+ hasOutput: t.boolean(),
1593
+ },
1594
+ events: {
1595
+ setInput: { value: t.string() },
1596
+ requestRun: {},
1597
+ reset: {},
1598
+ },
1599
+ requirements: {
1600
+ RUN_AGENT: { input: t.string() },
1601
+ },
1602
+ } satisfies ModuleSchema;
1603
+
1604
+ // ============================================================================
1605
+ // Module
1606
+ // ============================================================================
1607
+
1608
+ export const ${camelName} = createModule("${name}", {
1609
+ schema,
1610
+
1611
+ init: (facts) => {
1612
+ facts.input = "";
1613
+ facts.output = "";
1614
+ facts.status = "idle";
1615
+ facts.error = "";
1616
+ facts.totalTokens = 0;
1617
+ },
1618
+
1619
+ derive: {
1620
+ isThinking: (facts) => facts.status === "thinking",
1621
+ hasOutput: (facts) => facts.output !== "",
1622
+ },
1623
+
1624
+ events: {
1625
+ setInput: (facts, { value }) => {
1626
+ facts.input = value;
1627
+ },
1628
+ requestRun: (facts) => {
1629
+ facts.status = "thinking";
1630
+ facts.output = "";
1631
+ facts.error = "";
1632
+ },
1633
+ reset: (facts) => {
1634
+ facts.input = "";
1635
+ facts.output = "";
1636
+ facts.status = "idle";
1637
+ facts.error = "";
1638
+ facts.totalTokens = 0;
1639
+ },
1640
+ },
1641
+
1642
+ constraints: {
1643
+ needsRun: {
1644
+ priority: 100,
1645
+ when: (facts) => facts.status === "thinking",
1646
+ require: (facts) => ({
1647
+ type: "RUN_AGENT",
1648
+ input: facts.input,
1649
+ }),
1650
+ },
1651
+ },
1652
+
1653
+ resolvers: {
1654
+ runAgent: {
1655
+ requirement: "RUN_AGENT",
1656
+ timeout: 30000,
1657
+ resolve: async (req, context) => {
1658
+ // TODO: Replace with your agent runner
1659
+ const result = \`Echo: \${req.input}\`;
1660
+
1661
+ context.facts.output = result;
1662
+ context.facts.status = "done";
1663
+ },
1664
+ },
1665
+ },
1666
+ });
1667
+
1668
+ // ============================================================================
1669
+ // AI Features
1670
+ // ============================================================================
1671
+
1672
+ export const memory = createAgentMemory({
1673
+ strategy: createSlidingWindowStrategy(),
1674
+ strategyConfig: { maxMessages: 30, preserveRecentCount: 6 },
1675
+ autoManage: true,
1676
+ });
1677
+
1678
+ // TODO: Add your agent runner and configure the orchestrator
1679
+ // export const orchestrator = createAgentOrchestrator({
1680
+ // runner: yourAgentRunner,
1681
+ // maxTokenBudget: 50000,
1682
+ // memory,
1683
+ // guardrails: {
1684
+ // input: [],
1685
+ // output: [],
1686
+ // },
1687
+ // });
1688
+
1689
+ // ============================================================================
1690
+ // System
1691
+ // ============================================================================
1692
+
1693
+ export const system = createSystem({
1694
+ module: ${camelName},
1695
+ });
1696
+ `;
1697
+ }
1698
+ function toCamelCase2(name) {
1699
+ return name.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
1700
+ }
1701
+ function writeFile3(filePath, content) {
1702
+ const dir = dirname(filePath);
1703
+ if (!existsSync(dir)) {
1704
+ mkdirSync(dir, { recursive: true });
1705
+ }
1706
+ writeFileSync(filePath, content, "utf-8");
1707
+ }
1708
+ function findModulesDir(dir) {
1709
+ if (existsSync(join(dir, "src"))) {
1710
+ return join(dir, "src");
1711
+ }
1712
+ return join(dir, "src");
1713
+ }
1714
+ async function newModuleCommand(name, args) {
1715
+ const opts = parseArgs3(args);
1716
+ if (!name || !/^[a-z][a-z0-9-]*$/.test(name)) {
1717
+ console.error(
1718
+ `Invalid module name: ${name || "(none)"}
1719
+ Must start with a letter, use lowercase letters, numbers, and hyphens.`
1720
+ );
1721
+ process.exit(1);
1722
+ }
1723
+ let sections;
1724
+ if (opts.minimal) {
1725
+ sections = [];
1726
+ } else if (opts.with.length > 0) {
1727
+ sections = opts.with.filter(
1728
+ (s) => ALL_SECTIONS.includes(s)
1729
+ );
1730
+ } else {
1731
+ sections = ALL_SECTIONS;
1732
+ }
1733
+ const targetDir = findModulesDir(opts.dir);
1734
+ const filePath = join(targetDir, `${name}.ts`);
1735
+ if (existsSync(filePath)) {
1736
+ console.error(`File already exists: ${relative(opts.dir, filePath)}`);
1737
+ process.exit(1);
1738
+ }
1739
+ const content = generateModule(name, sections);
1740
+ writeFile3(filePath, content);
1741
+ const rel = relative(opts.dir, filePath);
1742
+ console.log(`${pc5.green("Created")} ${pc5.dim(rel)}`);
1743
+ if (sections.length === 0) {
1744
+ console.log(pc5.dim(" Minimal module (schema + init only)"));
1745
+ } else {
1746
+ console.log(pc5.dim(` Sections: ${sections.join(", ")}`));
1747
+ }
1748
+ }
1749
+ async function newOrchestratorCommand(name, args) {
1750
+ const opts = parseArgs3(args);
1751
+ if (!name || !/^[a-z][a-z0-9-]*$/.test(name)) {
1752
+ console.error(
1753
+ `Invalid orchestrator name: ${name || "(none)"}
1754
+ Must start with a letter, use lowercase letters, numbers, and hyphens.`
1755
+ );
1756
+ process.exit(1);
1757
+ }
1758
+ const targetDir = findModulesDir(opts.dir);
1759
+ const filePath = join(targetDir, `${name}.ts`);
1760
+ if (existsSync(filePath)) {
1761
+ console.error(`File already exists: ${relative(opts.dir, filePath)}`);
1762
+ process.exit(1);
1763
+ }
1764
+ const content = generateOrchestrator(name);
1765
+ writeFile3(filePath, content);
1766
+ const rel = relative(opts.dir, filePath);
1767
+ console.log(`${pc5.green("Created")} ${pc5.dim(rel)}`);
1768
+ console.log(pc5.dim(" AI orchestrator with memory, guardrails, and streaming"));
1769
+ }
1770
+ async function loadSystem(filePath) {
1771
+ const resolved = resolve(filePath);
1772
+ if (!existsSync(resolved)) {
1773
+ throw new Error(`File not found: ${resolved}`);
1774
+ }
1775
+ try {
1776
+ const mod = await import(resolved);
1777
+ if (mod.default && isSystem(mod.default)) {
1778
+ return mod.default;
1779
+ }
1780
+ if (mod.system && isSystem(mod.system)) {
1781
+ return mod.system;
1782
+ }
1783
+ for (const key of Object.keys(mod)) {
1784
+ if (isSystem(mod[key])) {
1785
+ return mod[key];
1786
+ }
1787
+ }
1788
+ throw new Error(
1789
+ `No Directive system found in ${pc5.dim(filePath)}
1790
+ Export a system as default or named "system":
1791
+
1792
+ ${pc5.cyan("export default")} createSystem({ module: myModule });
1793
+ ${pc5.cyan("export const system")} = createSystem({ module: myModule });`
1794
+ );
1795
+ } catch (err) {
1796
+ if (err instanceof Error && err.message.includes("No Directive system")) {
1797
+ throw err;
1798
+ }
1799
+ throw new Error(
1800
+ `Failed to load ${pc5.dim(filePath)}: ${err instanceof Error ? err.message : String(err)}
1801
+
1802
+ Make sure the file is valid TypeScript and tsx is installed:
1803
+ ${pc5.cyan("npm install -D tsx")}`
1804
+ );
1805
+ }
1806
+ }
1807
+ function isSystem(obj) {
1808
+ if (typeof obj !== "object" || obj === null) {
1809
+ return false;
1810
+ }
1811
+ const sys = obj;
1812
+ return typeof sys.inspect === "function" && typeof sys.start === "function" && typeof sys.stop === "function" && "facts" in sys;
1813
+ }
1814
+
1815
+ // src/commands/inspect.ts
1816
+ function parseArgs4(args) {
1817
+ const opts = { json: false };
1818
+ let filePath = "";
1819
+ for (let i = 0; i < args.length; i++) {
1820
+ const arg = args[i];
1821
+ switch (arg) {
1822
+ case "--json":
1823
+ opts.json = true;
1824
+ break;
1825
+ case "--module": {
1826
+ const val = args[++i];
1827
+ if (val) {
1828
+ opts.module = val;
1829
+ }
1830
+ break;
1831
+ }
1832
+ default:
1833
+ if (arg && !arg.startsWith("-") && !filePath) {
1834
+ filePath = arg;
1835
+ }
1836
+ }
1837
+ }
1838
+ return { filePath, opts };
1839
+ }
1840
+ function formatFacts(facts) {
1841
+ const lines = [];
1842
+ lines.push(pc5.bold("Facts:"));
1843
+ const entries = Object.entries(facts);
1844
+ if (entries.length === 0) {
1845
+ lines.push(" (none)");
1846
+ return lines.join("\n");
1847
+ }
1848
+ for (const [key, value] of entries) {
1849
+ const formatted = formatValue(value);
1850
+ lines.push(` ${pc5.cyan(key)} = ${formatted}`);
1851
+ }
1852
+ return lines.join("\n");
1853
+ }
1854
+ function formatConstraints(constraints) {
1855
+ const lines = [];
1856
+ lines.push(pc5.bold("Constraints:"));
1857
+ if (constraints.length === 0) {
1858
+ lines.push(" (none)");
1859
+ return lines.join("\n");
1860
+ }
1861
+ for (const c of constraints) {
1862
+ const status = c.disabled ? pc5.dim("disabled") : c.active ? pc5.green("active") : pc5.dim("inactive");
1863
+ const hits = c.hitCount > 0 ? pc5.yellow(` (${c.hitCount} hits)`) : "";
1864
+ lines.push(
1865
+ ` ${pc5.cyan(c.id)} ${status} priority=${c.priority}${hits}`
1866
+ );
1867
+ }
1868
+ return lines.join("\n");
1869
+ }
1870
+ function formatResolverDefs(resolverDefs, resolvers) {
1871
+ const lines = [];
1872
+ lines.push(pc5.bold("Resolvers:"));
1873
+ if (resolverDefs.length === 0) {
1874
+ lines.push(" (none)");
1875
+ return lines.join("\n");
1876
+ }
1877
+ for (const def of resolverDefs) {
1878
+ const status = resolvers[def.id];
1879
+ const stateStr = status ? formatResolverState(status.state, status.error, status.duration) : pc5.dim("idle");
1880
+ lines.push(
1881
+ ` ${pc5.cyan(def.id)} \u2192 ${def.requirement} ${stateStr}`
1882
+ );
1883
+ }
1884
+ return lines.join("\n");
1885
+ }
1886
+ function formatUnmet(unmet) {
1887
+ const lines = [];
1888
+ lines.push(pc5.bold("Unmet Requirements:"));
1889
+ if (unmet.length === 0) {
1890
+ lines.push(` ${pc5.green("(all requirements met)")}`);
1891
+ return lines.join("\n");
1892
+ }
1893
+ for (const u of unmet) {
1894
+ lines.push(
1895
+ ` ${pc5.yellow(u.requirement.type)} (id: ${pc5.dim(u.id)}) from ${pc5.dim(u.fromConstraint)}`
1896
+ );
1897
+ }
1898
+ return lines.join("\n");
1899
+ }
1900
+ function formatInflight(inflight) {
1901
+ const lines = [];
1902
+ lines.push(pc5.bold("Inflight:"));
1903
+ if (inflight.length === 0) {
1904
+ lines.push(` ${pc5.green("(none)")}`);
1905
+ return lines.join("\n");
1906
+ }
1907
+ const now = Date.now();
1908
+ for (const inf of inflight) {
1909
+ const elapsed = now - inf.startedAt;
1910
+ lines.push(
1911
+ ` ${pc5.cyan(inf.resolverId)} \u2192 req ${pc5.dim(inf.id)} ${pc5.yellow(`${elapsed}ms`)}`
1912
+ );
1913
+ }
1914
+ return lines.join("\n");
1915
+ }
1916
+ function formatResolverState(state, error, duration) {
1917
+ const dur = duration !== void 0 ? ` ${duration}ms` : "";
1918
+ switch (state) {
1919
+ case "resolved":
1920
+ return pc5.green(`resolved${dur}`);
1921
+ case "errored":
1922
+ return pc5.red(`errored${dur}${error ? ` \u2014 ${error}` : ""}`);
1923
+ case "inflight":
1924
+ return pc5.yellow("inflight");
1925
+ case "cancelled":
1926
+ return pc5.dim("cancelled");
1927
+ default:
1928
+ return pc5.dim(state);
1929
+ }
1930
+ }
1931
+ function formatValue(value) {
1932
+ if (value === null) {
1933
+ return pc5.dim("null");
1934
+ }
1935
+ if (value === void 0) {
1936
+ return pc5.dim("undefined");
1937
+ }
1938
+ if (typeof value === "string") {
1939
+ return value.length > 60 ? `"${value.slice(0, 57)}..."` : `"${value}"`;
1940
+ }
1941
+ if (typeof value === "number" || typeof value === "boolean") {
1942
+ return String(value);
1943
+ }
1944
+ if (Array.isArray(value)) {
1945
+ return `[${value.length} items]`;
1946
+ }
1947
+ return JSON.stringify(value).slice(0, 60);
1948
+ }
1949
+ function findWarnings(inspection) {
1950
+ const warnings = [];
1951
+ new Set(inspection.unmet.map((u) => u.requirement.type));
1952
+ new Set(
1953
+ inspection.resolverDefs.map((r) => r.requirement)
1954
+ );
1955
+ for (const def of inspection.resolverDefs) {
1956
+ if (def.requirement === "(predicate)") {
1957
+ continue;
1958
+ }
1959
+ }
1960
+ for (const u of inspection.unmet) {
1961
+ const hasResolver = inspection.resolverDefs.some(
1962
+ (r) => r.requirement === u.requirement.type || r.requirement === "(predicate)"
1963
+ );
1964
+ if (!hasResolver) {
1965
+ warnings.push(
1966
+ `No resolver for requirement type "${u.requirement.type}"`
1967
+ );
1968
+ }
1969
+ }
1970
+ return warnings;
1971
+ }
1972
+ async function inspectCommand(args) {
1973
+ const { filePath, opts } = parseArgs4(args);
1974
+ if (!filePath) {
1975
+ console.error(
1976
+ "Usage: directive inspect <file> [--json] [--module <name>]"
1977
+ );
1978
+ process.exit(1);
1979
+ }
1980
+ const system = await loadSystem(filePath);
1981
+ if (!system.isRunning) {
1982
+ system.start();
1983
+ }
1984
+ const inspection = system.inspect();
1985
+ if (opts.json) {
1986
+ const factsObj2 = {};
1987
+ if (system.facts) {
1988
+ for (const key of Object.keys(system.facts)) {
1989
+ try {
1990
+ factsObj2[key] = system.facts[key];
1991
+ } catch {
1992
+ factsObj2[key] = "(error reading)";
1993
+ }
1994
+ }
1995
+ }
1996
+ console.log(
1997
+ JSON.stringify(
1998
+ {
1999
+ facts: factsObj2,
2000
+ ...inspection
2001
+ },
2002
+ null,
2003
+ 2
2004
+ )
2005
+ );
2006
+ system.stop();
2007
+ return;
2008
+ }
2009
+ console.log();
2010
+ console.log(pc5.bold(pc5.cyan("Directive System Inspection")));
2011
+ console.log(pc5.dim("\u2500".repeat(40)));
2012
+ console.log();
2013
+ const factsObj = {};
2014
+ if (system.facts) {
2015
+ for (const key of Object.keys(system.facts)) {
2016
+ try {
2017
+ factsObj[key] = system.facts[key];
2018
+ } catch {
2019
+ factsObj[key] = "(error reading)";
2020
+ }
2021
+ }
2022
+ }
2023
+ console.log(formatFacts(factsObj));
2024
+ console.log();
2025
+ console.log(formatConstraints(inspection.constraints));
2026
+ console.log();
2027
+ console.log(formatResolverDefs(inspection.resolverDefs, inspection.resolvers));
2028
+ console.log();
2029
+ console.log(formatUnmet(inspection.unmet));
2030
+ console.log();
2031
+ if (inspection.inflight.length > 0) {
2032
+ console.log(formatInflight(inspection.inflight));
2033
+ console.log();
2034
+ }
2035
+ const warnings = findWarnings(inspection);
2036
+ if (warnings.length > 0) {
2037
+ console.log(pc5.bold(pc5.yellow("Warnings:")));
2038
+ for (const w of warnings) {
2039
+ console.log(` ${pc5.yellow("\u26A0")} ${w}`);
2040
+ }
2041
+ console.log();
2042
+ }
2043
+ system.stop();
2044
+ }
2045
+ function parseArgs5(args) {
2046
+ const opts = {};
2047
+ let filePath = "";
2048
+ let requirementId;
2049
+ for (let i = 0; i < args.length; i++) {
2050
+ const arg = args[i];
2051
+ switch (arg) {
2052
+ case "--module": {
2053
+ const val = args[++i];
2054
+ if (val) {
2055
+ opts.module = val;
2056
+ }
2057
+ break;
2058
+ }
2059
+ default:
2060
+ if (arg && !arg.startsWith("-")) {
2061
+ if (!filePath) {
2062
+ filePath = arg;
2063
+ } else if (!requirementId) {
2064
+ requirementId = arg;
2065
+ }
2066
+ }
2067
+ }
2068
+ }
2069
+ return { filePath, requirementId, opts };
2070
+ }
2071
+ async function explainCommand(args) {
2072
+ const { filePath, requirementId } = parseArgs5(args);
2073
+ if (!filePath) {
2074
+ console.error(
2075
+ "Usage: directive explain <file> [requirement-id]"
2076
+ );
2077
+ process.exit(1);
2078
+ }
2079
+ const system = await loadSystem(filePath);
2080
+ if (!system.isRunning) {
2081
+ system.start();
2082
+ }
2083
+ const inspection = system.inspect();
2084
+ if (requirementId) {
2085
+ const explanation = system.explain(requirementId);
2086
+ if (!explanation) {
2087
+ console.error(
2088
+ `Requirement "${requirementId}" not found.
2089
+
2090
+ Current requirements:`
2091
+ );
2092
+ if (inspection.unmet.length === 0) {
2093
+ console.log(pc5.dim(" (no unmet requirements)"));
2094
+ } else {
2095
+ for (const u of inspection.unmet) {
2096
+ console.log(
2097
+ ` ${pc5.cyan(u.id)} \u2014 ${u.requirement.type} (from ${u.fromConstraint})`
2098
+ );
2099
+ }
2100
+ }
2101
+ system.stop();
2102
+ process.exit(1);
2103
+ }
2104
+ console.log();
2105
+ console.log(pc5.bold(pc5.cyan("Requirement Explanation")));
2106
+ console.log(pc5.dim("\u2500".repeat(40)));
2107
+ console.log();
2108
+ console.log(explanation);
2109
+ console.log();
2110
+ } else {
2111
+ console.log();
2112
+ console.log(pc5.bold(pc5.cyan("All Requirements")));
2113
+ console.log(pc5.dim("\u2500".repeat(40)));
2114
+ console.log();
2115
+ if (inspection.unmet.length === 0) {
2116
+ console.log(pc5.green("All requirements are met."));
2117
+ console.log();
2118
+ const resolverEntries = Object.entries(
2119
+ inspection.resolvers
2120
+ );
2121
+ if (resolverEntries.length > 0) {
2122
+ console.log(pc5.bold("Recent Resolver Activity:"));
2123
+ for (const [key, status] of resolverEntries) {
2124
+ const state = formatState(status.state);
2125
+ const dur = status.duration !== void 0 ? ` (${status.duration}ms)` : "";
2126
+ console.log(` ${pc5.cyan(key)} ${state}${dur}`);
2127
+ }
2128
+ console.log();
2129
+ }
2130
+ } else {
2131
+ console.log(
2132
+ `${pc5.yellow(String(inspection.unmet.length))} unmet requirement(s):
2133
+ `
2134
+ );
2135
+ for (const u of inspection.unmet) {
2136
+ console.log(
2137
+ `${pc5.yellow("\u25CF")} ${pc5.bold(u.requirement.type)} (id: ${pc5.dim(u.id)})`
2138
+ );
2139
+ console.log(` From constraint: ${pc5.cyan(u.fromConstraint)}`);
2140
+ const payload = { ...u.requirement };
2141
+ delete payload.type;
2142
+ const payloadKeys = Object.keys(payload);
2143
+ if (payloadKeys.length > 0) {
2144
+ console.log(` Payload: ${JSON.stringify(payload)}`);
2145
+ }
2146
+ const resolverStatus = inspection.resolvers[u.id];
2147
+ if (resolverStatus) {
2148
+ console.log(
2149
+ ` Resolver: ${formatState(resolverStatus.state)}${resolverStatus.error ? ` \u2014 ${resolverStatus.error}` : ""}`
2150
+ );
2151
+ } else {
2152
+ const hasResolver = inspection.resolverDefs.some(
2153
+ (r) => r.requirement === u.requirement.type || r.requirement === "(predicate)"
2154
+ );
2155
+ if (!hasResolver) {
2156
+ console.log(` ${pc5.red("No resolver registered for this type")}`);
2157
+ }
2158
+ }
2159
+ console.log();
2160
+ }
2161
+ console.log(
2162
+ pc5.dim(
2163
+ `Run ${pc5.cyan(`directive explain <file> <requirement-id>`)} for detailed explanation.`
2164
+ )
2165
+ );
2166
+ }
2167
+ }
2168
+ system.stop();
2169
+ }
2170
+ function formatState(state) {
2171
+ switch (state) {
2172
+ case "resolved":
2173
+ return pc5.green("resolved");
2174
+ case "errored":
2175
+ return pc5.red("errored");
2176
+ case "inflight":
2177
+ return pc5.yellow("inflight");
2178
+ case "pending":
2179
+ return pc5.yellow("pending");
2180
+ case "cancelled":
2181
+ return pc5.dim("cancelled");
2182
+ default:
2183
+ return pc5.dim(state);
2184
+ }
2185
+ }
2186
+ function parseArgs6(args) {
2187
+ const opts = { ascii: false, open: true };
2188
+ let filePath = "";
2189
+ for (let i = 0; i < args.length; i++) {
2190
+ const arg = args[i];
2191
+ switch (arg) {
2192
+ case "--ascii":
2193
+ opts.ascii = true;
2194
+ break;
2195
+ case "--no-open":
2196
+ opts.open = false;
2197
+ break;
2198
+ case "--output": {
2199
+ const val = args[++i];
2200
+ if (val) {
2201
+ opts.output = val;
2202
+ }
2203
+ break;
2204
+ }
2205
+ default:
2206
+ if (arg && !arg.startsWith("-") && !filePath) {
2207
+ filePath = arg;
2208
+ }
2209
+ }
2210
+ }
2211
+ return { filePath, opts };
2212
+ }
2213
+ function renderAsciiGraph(inspection) {
2214
+ const lines = [];
2215
+ lines.push(pc5.bold("Dependency Graph"));
2216
+ lines.push(pc5.dim("\u2550".repeat(50)));
2217
+ lines.push("");
2218
+ const constraintMap = /* @__PURE__ */ new Map();
2219
+ for (const c of inspection.constraints) {
2220
+ constraintMap.set(c.id, {
2221
+ reqTypes: /* @__PURE__ */ new Set(),
2222
+ active: c.active,
2223
+ priority: c.priority
2224
+ });
2225
+ }
2226
+ for (const u of inspection.unmet) {
2227
+ const entry = constraintMap.get(u.fromConstraint);
2228
+ if (entry) {
2229
+ entry.reqTypes.add(u.requirement.type);
2230
+ }
2231
+ }
2232
+ const resolversByType = /* @__PURE__ */ new Map();
2233
+ for (const r of inspection.resolverDefs) {
2234
+ if (!resolversByType.has(r.requirement)) {
2235
+ resolversByType.set(r.requirement, []);
2236
+ }
2237
+ resolversByType.get(r.requirement).push(r.id);
2238
+ }
2239
+ lines.push(pc5.bold("Constraints \u2192 Requirements \u2192 Resolvers"));
2240
+ lines.push("");
2241
+ for (const [id, info] of constraintMap) {
2242
+ const status = info.active ? pc5.green("\u25CF") : pc5.dim("\u25CB");
2243
+ lines.push(`${status} ${pc5.cyan(id)} (priority: ${info.priority})`);
2244
+ if (info.reqTypes.size > 0) {
2245
+ for (const reqType of info.reqTypes) {
2246
+ lines.push(` \u2514\u2500\u25B6 ${pc5.yellow(reqType)}`);
2247
+ const resolvers = resolversByType.get(reqType) || [];
2248
+ if (resolvers.length > 0) {
2249
+ for (const r of resolvers) {
2250
+ lines.push(` \u2514\u2500\u25B6 ${pc5.magenta(r)}`);
2251
+ }
2252
+ } else {
2253
+ lines.push(` \u2514\u2500\u25B6 ${pc5.red("(no resolver)")}`);
2254
+ }
2255
+ }
2256
+ } else {
2257
+ lines.push(` \u2514\u2500\u25B6 ${pc5.dim("(no active requirements)")}`);
2258
+ }
2259
+ lines.push("");
2260
+ }
2261
+ const usedResolvers = /* @__PURE__ */ new Set();
2262
+ for (const resolvers of resolversByType.values()) {
2263
+ for (const r of resolvers) {
2264
+ usedResolvers.add(r);
2265
+ }
2266
+ }
2267
+ const allResolverIds = inspection.resolverDefs.map((r) => r.id);
2268
+ const orphanedCount = allResolverIds.filter(
2269
+ (r) => !usedResolvers.has(r)
2270
+ ).length;
2271
+ if (orphanedCount > 0) {
2272
+ lines.push(pc5.bold("Standalone Resolvers:"));
2273
+ for (const r of inspection.resolverDefs) {
2274
+ if (!usedResolvers.has(r.id)) {
2275
+ lines.push(
2276
+ ` ${pc5.magenta(r.id)} handles ${pc5.yellow(r.requirement)}`
2277
+ );
2278
+ }
2279
+ }
2280
+ }
2281
+ return lines.join("\n");
2282
+ }
2283
+ function renderHtmlGraph(inspection, facts) {
2284
+ const nodes = [];
2285
+ const edges = [];
2286
+ const colWidth = 220;
2287
+ const rowHeight = 50;
2288
+ const startX = 40;
2289
+ const startY = 60;
2290
+ const factKeys = Object.keys(facts);
2291
+ for (let i = 0; i < factKeys.length; i++) {
2292
+ const key = factKeys[i];
2293
+ nodes.push({
2294
+ id: `fact-${key}`,
2295
+ label: key,
2296
+ type: "fact",
2297
+ x: startX,
2298
+ y: startY + i * rowHeight,
2299
+ color: "#3b82f6"
2300
+ });
2301
+ }
2302
+ for (let i = 0; i < inspection.constraints.length; i++) {
2303
+ const c = inspection.constraints[i];
2304
+ nodes.push({
2305
+ id: `constraint-${c.id}`,
2306
+ label: c.id,
2307
+ type: "constraint",
2308
+ x: startX + colWidth,
2309
+ y: startY + i * rowHeight,
2310
+ color: c.active ? "#22c55e" : "#6b7280"
2311
+ });
2312
+ }
2313
+ const reqTypes = /* @__PURE__ */ new Set();
2314
+ for (const u of inspection.unmet) {
2315
+ reqTypes.add(u.requirement.type);
2316
+ }
2317
+ let reqIdx = 0;
2318
+ for (const reqType of reqTypes) {
2319
+ nodes.push({
2320
+ id: `req-${reqType}`,
2321
+ label: reqType,
2322
+ type: "requirement",
2323
+ x: startX + colWidth * 2,
2324
+ y: startY + reqIdx * rowHeight,
2325
+ color: "#eab308"
2326
+ });
2327
+ reqIdx++;
2328
+ }
2329
+ for (let i = 0; i < inspection.resolverDefs.length; i++) {
2330
+ const r = inspection.resolverDefs[i];
2331
+ nodes.push({
2332
+ id: `resolver-${r.id}`,
2333
+ label: r.id,
2334
+ type: "resolver",
2335
+ x: startX + colWidth * 3,
2336
+ y: startY + i * rowHeight,
2337
+ color: "#a855f7"
2338
+ });
2339
+ }
2340
+ for (const u of inspection.unmet) {
2341
+ edges.push({
2342
+ from: `constraint-${u.fromConstraint}`,
2343
+ to: `req-${u.requirement.type}`
2344
+ });
2345
+ }
2346
+ for (const r of inspection.resolverDefs) {
2347
+ if (reqTypes.has(r.requirement)) {
2348
+ edges.push({
2349
+ from: `req-${r.requirement}`,
2350
+ to: `resolver-${r.id}`
2351
+ });
2352
+ }
2353
+ }
2354
+ const nodeMap = new Map(nodes.map((n) => [n.id, n]));
2355
+ const svgWidth = startX + colWidth * 4 + 40;
2356
+ const maxY = Math.max(...nodes.map((n) => n.y)) + rowHeight + 20;
2357
+ const edgeSvg = edges.map((e) => {
2358
+ const from = nodeMap.get(e.from);
2359
+ const to = nodeMap.get(e.to);
2360
+ if (!from || !to) {
2361
+ return "";
2362
+ }
2363
+ return `<line x1="${from.x + 90}" y1="${from.y + 15}" x2="${to.x}" y2="${to.y + 15}" stroke="#94a3b8" stroke-width="1.5" marker-end="url(#arrow)"/>`;
2364
+ }).join("\n ");
2365
+ const nodeSvg = nodes.map(
2366
+ (n) => `<g>
2367
+ <rect x="${n.x}" y="${n.y}" width="180" height="30" rx="6" fill="${n.color}" opacity="0.15" stroke="${n.color}" stroke-width="1.5"/>
2368
+ <text x="${n.x + 90}" y="${n.y + 19}" text-anchor="middle" font-size="12" font-family="monospace" fill="${n.color}">${escapeHtml(n.label)}</text>
2369
+ </g>`
2370
+ ).join("\n ");
2371
+ const headers = ["Facts", "Constraints", "Requirements", "Resolvers"];
2372
+ const headerSvg = headers.map(
2373
+ (h, i) => `<text x="${startX + i * colWidth + 90}" y="35" text-anchor="middle" font-size="14" font-weight="bold" font-family="system-ui" fill="#e2e8f0">${h}</text>`
2374
+ ).join("\n ");
2375
+ return `<!DOCTYPE html>
2376
+ <html>
2377
+ <head>
2378
+ <title>Directive System Graph</title>
2379
+ <style>
2380
+ body { margin: 0; background: #0f172a; display: flex; justify-content: center; padding: 20px; }
2381
+ svg { max-width: 100%; }
2382
+ </style>
2383
+ </head>
2384
+ <body>
2385
+ <svg width="${svgWidth}" height="${maxY}" xmlns="http://www.w3.org/2000/svg">
2386
+ <defs>
2387
+ <marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
2388
+ <path d="M 0 0 L 10 5 L 0 10 z" fill="#94a3b8"/>
2389
+ </marker>
2390
+ </defs>
2391
+ ${headerSvg}
2392
+ ${edgeSvg}
2393
+ ${nodeSvg}
2394
+ </svg>
2395
+ </body>
2396
+ </html>`;
2397
+ }
2398
+ function escapeHtml(text2) {
2399
+ return text2.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
2400
+ }
2401
+ async function graphCommand(args) {
2402
+ const { filePath, opts } = parseArgs6(args);
2403
+ if (!filePath) {
2404
+ console.error("Usage: directive graph <file> [--ascii] [--no-open] [--output <path>]");
2405
+ process.exit(1);
2406
+ }
2407
+ const system = await loadSystem(filePath);
2408
+ if (!system.isRunning) {
2409
+ system.start();
2410
+ }
2411
+ const inspection = system.inspect();
2412
+ if (opts.ascii) {
2413
+ console.log(renderAsciiGraph(inspection));
2414
+ system.stop();
2415
+ return;
2416
+ }
2417
+ const factsObj = {};
2418
+ if (system.facts) {
2419
+ for (const key of Object.keys(system.facts)) {
2420
+ try {
2421
+ factsObj[key] = system.facts[key];
2422
+ } catch {
2423
+ factsObj[key] = null;
2424
+ }
2425
+ }
2426
+ }
2427
+ const html = renderHtmlGraph(inspection, factsObj);
2428
+ const outputPath = opts.output || join(process.cwd(), ".directive-graph.html");
2429
+ writeFileSync(outputPath, html, "utf-8");
2430
+ console.log(`${pc5.green("Generated")} ${pc5.dim(outputPath)}`);
2431
+ if (opts.open) {
2432
+ try {
2433
+ const { exec } = await import('child_process');
2434
+ const openCmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
2435
+ exec(`${openCmd} "${outputPath}"`);
2436
+ console.log(pc5.dim("Opened in browser."));
2437
+ } catch {
2438
+ console.log(
2439
+ pc5.dim(`Open ${outputPath} in your browser to view the graph.`)
2440
+ );
2441
+ }
2442
+ }
2443
+ system.stop();
2444
+ }
2445
+ function parseArgs7(args) {
2446
+ const opts = { dir: process.cwd() };
2447
+ for (let i = 0; i < args.length; i++) {
2448
+ if (args[i] === "--dir") {
2449
+ const val = args[++i];
2450
+ if (val) {
2451
+ opts.dir = val;
2452
+ }
2453
+ }
2454
+ }
2455
+ return opts;
2456
+ }
2457
+ function checkCoreInstalled(dir) {
2458
+ const pkgPath = join(dir, "package.json");
2459
+ if (!existsSync(pkgPath)) {
2460
+ return {
2461
+ label: "@directive-run/core installed",
2462
+ passed: false,
2463
+ message: "No package.json found",
2464
+ fix: "Run `npm init` to create a package.json"
2465
+ };
2466
+ }
2467
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
2468
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
2469
+ if (!deps["@directive-run/core"]) {
2470
+ return {
2471
+ label: "@directive-run/core installed",
2472
+ passed: false,
2473
+ message: "Not found in dependencies",
2474
+ fix: "Run `npm install @directive-run/core`"
2475
+ };
2476
+ }
2477
+ return {
2478
+ label: "@directive-run/core installed",
2479
+ passed: true,
2480
+ message: `v${deps["@directive-run/core"]}`
2481
+ };
2482
+ }
2483
+ function checkVersionCompatibility(dir) {
2484
+ const pkgPath = join(dir, "package.json");
2485
+ if (!existsSync(pkgPath)) {
2486
+ return {
2487
+ label: "Package version compatibility",
2488
+ passed: true,
2489
+ message: "Skipped (no package.json)"
2490
+ };
2491
+ }
2492
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
2493
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
2494
+ const directivePackages = Object.keys(deps).filter(
2495
+ (k) => k.startsWith("@directive-run/")
2496
+ );
2497
+ if (directivePackages.length <= 1) {
2498
+ return {
2499
+ label: "Package version compatibility",
2500
+ passed: true,
2501
+ message: directivePackages.length === 0 ? "No packages found" : "Single package"
2502
+ };
2503
+ }
2504
+ return {
2505
+ label: "Package version compatibility",
2506
+ passed: true,
2507
+ message: `${directivePackages.length} packages: ${directivePackages.join(", ")}`
2508
+ };
2509
+ }
2510
+ function checkTypeScript(dir) {
2511
+ const tsconfigPath = join(dir, "tsconfig.json");
2512
+ if (!existsSync(tsconfigPath)) {
2513
+ return {
2514
+ label: "TypeScript configuration",
2515
+ passed: false,
2516
+ message: "No tsconfig.json found",
2517
+ fix: "Run `tsc --init` to create a TypeScript configuration"
2518
+ };
2519
+ }
2520
+ try {
2521
+ const raw = readFileSync(tsconfigPath, "utf-8");
2522
+ const stripped = raw.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
2523
+ const config = JSON.parse(stripped);
2524
+ const co = config.compilerOptions || {};
2525
+ const issues = [];
2526
+ if (co.strict !== true) {
2527
+ issues.push("strict mode not enabled");
2528
+ }
2529
+ if (co.moduleResolution && !["bundler", "nodenext", "node16"].includes(
2530
+ co.moduleResolution.toLowerCase()
2531
+ )) {
2532
+ issues.push(`moduleResolution is "${co.moduleResolution}"`);
2533
+ }
2534
+ if (issues.length > 0) {
2535
+ return {
2536
+ label: "TypeScript configuration",
2537
+ passed: false,
2538
+ message: issues.join(", "),
2539
+ fix: 'Set "strict": true and "moduleResolution": "bundler" in tsconfig.json'
2540
+ };
2541
+ }
2542
+ return {
2543
+ label: "TypeScript configuration",
2544
+ passed: true,
2545
+ message: "strict mode, correct module resolution"
2546
+ };
2547
+ } catch {
2548
+ return {
2549
+ label: "TypeScript configuration",
2550
+ passed: true,
2551
+ message: "Found (could not parse for detailed checks)"
2552
+ };
2553
+ }
2554
+ }
2555
+ function checkDuplicateInstances(dir) {
2556
+ const nodeModules = join(dir, "node_modules");
2557
+ if (!existsSync(nodeModules)) {
2558
+ return {
2559
+ label: "No duplicate Directive instances",
2560
+ passed: true,
2561
+ message: "No node_modules found"
2562
+ };
2563
+ }
2564
+ const duplicates = [];
2565
+ try {
2566
+ const scopeDir = join(nodeModules, "@directive-run");
2567
+ if (existsSync(scopeDir)) {
2568
+ const packages = readdirSync(scopeDir);
2569
+ for (const pkg of packages) {
2570
+ const nestedCore = join(
2571
+ scopeDir,
2572
+ pkg,
2573
+ "node_modules",
2574
+ "@directive-run",
2575
+ "core"
2576
+ );
2577
+ if (existsSync(nestedCore)) {
2578
+ duplicates.push(`@directive-run/${pkg}/node_modules/@directive-run/core`);
2579
+ }
2580
+ }
2581
+ }
2582
+ } catch {
2583
+ }
2584
+ if (duplicates.length > 0) {
2585
+ return {
2586
+ label: "No duplicate Directive instances",
2587
+ passed: false,
2588
+ message: `Found ${duplicates.length} duplicate(s): ${duplicates.join(", ")}`,
2589
+ fix: "Run `npm dedupe` or check for version mismatches"
2590
+ };
2591
+ }
2592
+ return {
2593
+ label: "No duplicate Directive instances",
2594
+ passed: true,
2595
+ message: "No duplicates detected"
2596
+ };
2597
+ }
2598
+ function checkAIRulesFreshness(dir) {
2599
+ const ruleFiles = [
2600
+ ".cursorrules",
2601
+ ".claude/CLAUDE.md",
2602
+ ".github/copilot-instructions.md",
2603
+ ".windsurfrules",
2604
+ ".clinerules"
2605
+ ];
2606
+ const found = [];
2607
+ for (const file of ruleFiles) {
2608
+ const filePath = join(dir, file);
2609
+ if (existsSync(filePath)) {
2610
+ const content = readFileSync(filePath, "utf-8");
2611
+ if (hasDirectiveSection(content)) {
2612
+ found.push(file);
2613
+ }
2614
+ }
2615
+ }
2616
+ if (found.length === 0) {
2617
+ return {
2618
+ label: "AI coding rules",
2619
+ passed: true,
2620
+ message: "Not installed (optional)"
2621
+ };
2622
+ }
2623
+ return {
2624
+ label: "AI coding rules",
2625
+ passed: true,
2626
+ message: `Installed for: ${found.join(", ")}`
2627
+ };
2628
+ }
2629
+ async function doctorCommand(args) {
2630
+ const opts = parseArgs7(args);
2631
+ console.log();
2632
+ console.log(pc5.bold(pc5.cyan("Directive Doctor")));
2633
+ console.log(pc5.dim("\u2500".repeat(40)));
2634
+ console.log();
2635
+ const checks = [
2636
+ checkCoreInstalled(opts.dir),
2637
+ checkVersionCompatibility(opts.dir),
2638
+ checkTypeScript(opts.dir),
2639
+ checkDuplicateInstances(opts.dir),
2640
+ checkAIRulesFreshness(opts.dir)
2641
+ ];
2642
+ let failures = 0;
2643
+ for (const check of checks) {
2644
+ const icon = check.passed ? pc5.green("\u2713") : pc5.red("\u2717");
2645
+ console.log(`${icon} ${pc5.bold(check.label)}`);
2646
+ console.log(` ${pc5.dim(check.message)}`);
2647
+ if (!check.passed && check.fix) {
2648
+ console.log(` ${pc5.yellow("Fix:")} ${check.fix}`);
2649
+ failures++;
2650
+ }
2651
+ console.log();
2652
+ }
2653
+ if (failures > 0) {
2654
+ console.log(
2655
+ pc5.yellow(`${failures} issue(s) found. See suggested fixes above.`)
2656
+ );
2657
+ process.exit(1);
2658
+ } else {
2659
+ console.log(pc5.green("All checks passed!"));
2660
+ }
2661
+ }
2662
+ var CATEGORIES = {
2663
+ "Getting Started": ["counter", "contact-form", "auth-flow"],
2664
+ "Core Patterns": [
2665
+ "async-chains",
2666
+ "batch-resolver",
2667
+ "debounce-constraints",
2668
+ "error-boundaries",
2669
+ "feature-flags",
2670
+ "multi-module",
2671
+ "optimistic-updates",
2672
+ "pagination",
2673
+ "permissions"
2674
+ ],
2675
+ "Real-World": [
2676
+ "dashboard-loader",
2677
+ "form-wizard",
2678
+ "newsletter",
2679
+ "notifications",
2680
+ "shopping-cart",
2681
+ "theme-locale",
2682
+ "url-sync",
2683
+ "websocket",
2684
+ "server"
2685
+ ],
2686
+ Games: ["checkers", "sudoku", "goal-heist", "ab-testing"],
2687
+ AI: [
2688
+ "ai-orchestrator",
2689
+ "ai-checkpoint",
2690
+ "ai-guardrails",
2691
+ "fraud-analysis",
2692
+ "provider-routing",
2693
+ "topic-guard",
2694
+ "dynamic-modules",
2695
+ "time-machine"
2696
+ ]
2697
+ };
2698
+ function getCategory(name) {
2699
+ for (const [cat, names] of Object.entries(CATEGORIES)) {
2700
+ if (names.includes(name)) {
2701
+ return cat;
2702
+ }
2703
+ }
2704
+ return "Other";
2705
+ }
2706
+ function getDescription(content) {
2707
+ const lines = content.split("\n");
2708
+ for (const line of lines) {
2709
+ const trimmed = line.trim();
2710
+ if (trimmed.startsWith("// Example:")) {
2711
+ continue;
2712
+ }
2713
+ if (trimmed.startsWith("// Source:")) {
2714
+ continue;
2715
+ }
2716
+ if (trimmed.startsWith("// Extracted")) {
2717
+ continue;
2718
+ }
2719
+ const jsdocMatch = trimmed.match(/^\*\s+(.+?)(?:\s*\*\/)?$/);
2720
+ if (jsdocMatch?.[1] && !jsdocMatch[1].startsWith("@")) {
2721
+ return jsdocMatch[1];
2722
+ }
2723
+ if (trimmed.startsWith("//") && trimmed.length > 3) {
2724
+ return trimmed.slice(2).trim();
2725
+ }
2726
+ if (trimmed !== "" && !trimmed.startsWith("//") && !trimmed.startsWith("/*") && !trimmed.startsWith("*")) {
2727
+ break;
2728
+ }
2729
+ }
2730
+ return "";
2731
+ }
2732
+ async function examplesListCommand(args) {
2733
+ let filter;
2734
+ for (let i = 0; i < args.length; i++) {
2735
+ if (args[i] === "--filter") {
2736
+ filter = args[++i]?.toLowerCase();
2737
+ }
2738
+ }
2739
+ const examples = getAllExamples();
2740
+ console.log();
2741
+ console.log(pc5.bold(pc5.cyan("Directive Examples")));
2742
+ console.log(pc5.dim("\u2500".repeat(50)));
2743
+ console.log();
2744
+ const byCategory = /* @__PURE__ */ new Map();
2745
+ for (const [name, content] of examples) {
2746
+ const cat = getCategory(name);
2747
+ if (filter && !cat.toLowerCase().includes(filter) && !name.includes(filter)) {
2748
+ continue;
2749
+ }
2750
+ if (!byCategory.has(cat)) {
2751
+ byCategory.set(cat, []);
2752
+ }
2753
+ byCategory.get(cat).push({ name, desc: getDescription(content) });
2754
+ }
2755
+ if (byCategory.size === 0) {
2756
+ console.log(pc5.dim("No examples match the filter."));
2757
+ return;
2758
+ }
2759
+ const categoryOrder = Object.keys(CATEGORIES);
2760
+ const sortedCategories = [...byCategory.keys()].sort(
2761
+ (a, b) => (categoryOrder.indexOf(a) ?? 99) - (categoryOrder.indexOf(b) ?? 99)
2762
+ );
2763
+ for (const cat of sortedCategories) {
2764
+ const items = byCategory.get(cat);
2765
+ console.log(pc5.bold(cat));
2766
+ for (const item of items) {
2767
+ const desc = item.desc ? pc5.dim(` \u2014 ${item.desc}`) : "";
2768
+ console.log(` ${pc5.cyan(item.name)}${desc}`);
2769
+ }
2770
+ console.log();
2771
+ }
2772
+ console.log(
2773
+ pc5.dim(`${examples.size} examples available. Run ${pc5.cyan("directive examples copy <name>")} to extract one.`)
2774
+ );
2775
+ }
2776
+ async function examplesCopyCommand(name, args) {
2777
+ let dest = process.cwd();
2778
+ for (let i = 0; i < args.length; i++) {
2779
+ if (args[i] === "--dest") {
2780
+ const val = args[++i];
2781
+ if (val) {
2782
+ dest = val;
2783
+ }
2784
+ }
2785
+ }
2786
+ if (!name) {
2787
+ console.error("Usage: directive examples copy <name> [--dest <dir>]");
2788
+ process.exit(1);
2789
+ }
2790
+ const content = getExample(name);
2791
+ if (!content) {
2792
+ console.error(`Example "${name}" not found.`);
2793
+ console.error(
2794
+ `Run ${pc5.cyan("directive examples list")} to see available examples.`
2795
+ );
2796
+ process.exit(1);
2797
+ }
2798
+ const rewritten = content.replace(
2799
+ /from\s+["']@directive-run\/core\/plugins["']/g,
2800
+ 'from "@directive-run/core/plugins"'
2801
+ ).replace(
2802
+ /from\s+["']@directive-run\/core["']/g,
2803
+ 'from "@directive-run/core"'
2804
+ ).replace(
2805
+ /from\s+["']@directive-run\/ai["']/g,
2806
+ 'from "@directive-run/ai"'
2807
+ );
2808
+ const filePath = join(dest, `${name}.ts`);
2809
+ if (existsSync(filePath)) {
2810
+ console.error(`File already exists: ${relative(process.cwd(), filePath)}`);
2811
+ process.exit(1);
2812
+ }
2813
+ const dir = dirname(filePath);
2814
+ if (!existsSync(dir)) {
2815
+ mkdirSync(dir, { recursive: true });
2816
+ }
2817
+ writeFileSync(filePath, rewritten, "utf-8");
2818
+ const rel = relative(process.cwd(), filePath);
2819
+ console.log(`${pc5.green("Copied")} ${pc5.cyan(name)} \u2192 ${pc5.dim(rel)}`);
2820
+ }
2821
+
2822
+ // src/cli.ts
2823
+ var HELP = `
2824
+ ${CLI_NAME} \u2014 CLI tools for Directive
2825
+
2826
+ Usage: ${CLI_NAME} <command> [options]
2827
+
2828
+ Commands:
2829
+ init Project scaffolding wizard
2830
+ new module <name> Generate a module file
2831
+ new orchestrator <name> Generate an AI orchestrator
2832
+ inspect <file> Runtime system introspection
2833
+ explain <file> [req-id] Explain why a requirement exists
2834
+ graph <file> Visual dependency graph
2835
+ doctor Health check for project setup
2836
+ ai-rules init Install AI coding rules
2837
+ ai-rules update Refresh rules to latest version
2838
+ ai-rules check Validate rules are current (CI)
2839
+ examples list Browse available examples
2840
+ examples copy <name> Extract example to project
2841
+
2842
+ Options:
2843
+ --help, -h Show this help message
2844
+ --version, -v Show version
2845
+
2846
+ init options:
2847
+ --template <name> Template: counter, auth-flow, ai-orchestrator
2848
+ --dir <path> Target directory (default: cwd)
2849
+ --no-interactive Skip prompts, use defaults
2850
+
2851
+ new module options:
2852
+ --with <sections> Sections: derive,events,constraints,resolvers,effects
2853
+ --minimal Schema + init only
2854
+ --dir <path> Target directory (default: cwd)
2855
+
2856
+ inspect options:
2857
+ --json Output as JSON
2858
+ --module <name> Inspect specific module
2859
+
2860
+ graph options:
2861
+ --ascii Terminal-only output
2862
+ --no-open Don't open in browser
2863
+ --output <path> Output file path
2864
+
2865
+ ai-rules init options:
2866
+ --force Overwrite existing files without asking
2867
+ --merge Use section markers to update only the Directive section
2868
+ --tool <name> Skip detection, install for specific tool(s)
2869
+ --dir <path> Target directory (default: cwd)
2870
+
2871
+ examples options:
2872
+ --filter <category> Filter by category or name
2873
+ --dest <dir> Destination directory for copy
2874
+ `.trim();
2875
+ async function main() {
2876
+ const args = process.argv.slice(2);
2877
+ if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
2878
+ console.log(HELP);
2879
+ process.exit(0);
2880
+ }
2881
+ if (args.includes("--version") || args.includes("-v")) {
2882
+ const { readFileSync: readFileSync4 } = await import('fs');
2883
+ const { fileURLToPath } = await import('url');
2884
+ const { dirname: dirname6, join: join9 } = await import('path');
2885
+ const __dirname = dirname6(fileURLToPath(import.meta.url));
2886
+ const pkg = JSON.parse(
2887
+ readFileSync4(join9(__dirname, "..", "package.json"), "utf-8")
2888
+ );
2889
+ console.log(pkg.version);
2890
+ process.exit(0);
2891
+ }
2892
+ const command = args[0];
2893
+ switch (command) {
2894
+ case "init": {
2895
+ await initCommand(args.slice(1));
2896
+ break;
2897
+ }
2898
+ case "new": {
2899
+ const subcommand = args[1];
2900
+ const name = args[2];
2901
+ if (subcommand === "module") {
2902
+ if (!name) {
2903
+ console.error("Usage: directive new module <name>");
2904
+ process.exit(1);
2905
+ }
2906
+ await newModuleCommand(name, args.slice(3));
2907
+ } else if (subcommand === "orchestrator") {
2908
+ if (!name) {
2909
+ console.error("Usage: directive new orchestrator <name>");
2910
+ process.exit(1);
2911
+ }
2912
+ await newOrchestratorCommand(name, args.slice(3));
2913
+ } else {
2914
+ console.error(
2915
+ `Unknown subcommand: ${subcommand ?? "(none)"}
2916
+ Usage: ${CLI_NAME} new module <name>
2917
+ ${CLI_NAME} new orchestrator <name>`
2918
+ );
2919
+ process.exit(1);
2920
+ }
2921
+ break;
2922
+ }
2923
+ case "inspect": {
2924
+ await inspectCommand(args.slice(1));
2925
+ break;
2926
+ }
2927
+ case "explain": {
2928
+ await explainCommand(args.slice(1));
2929
+ break;
2930
+ }
2931
+ case "graph": {
2932
+ await graphCommand(args.slice(1));
2933
+ break;
2934
+ }
2935
+ case "doctor": {
2936
+ await doctorCommand(args.slice(1));
2937
+ break;
2938
+ }
2939
+ case "ai-rules": {
2940
+ const subcommand = args[1];
2941
+ if (subcommand === "init") {
2942
+ await aiRulesCommand(args.slice(2));
2943
+ } else if (subcommand === "update") {
2944
+ await aiRulesUpdateCommand(args.slice(2));
2945
+ } else if (subcommand === "check") {
2946
+ await aiRulesCheckCommand(args.slice(2));
2947
+ } else {
2948
+ console.error(
2949
+ `Unknown subcommand: ${subcommand ?? "(none)"}
2950
+ Usage: ${CLI_NAME} ai-rules init
2951
+ ${CLI_NAME} ai-rules update
2952
+ ${CLI_NAME} ai-rules check`
2953
+ );
2954
+ process.exit(1);
2955
+ }
2956
+ break;
2957
+ }
2958
+ case "examples": {
2959
+ const subcommand = args[1];
2960
+ if (subcommand === "list") {
2961
+ await examplesListCommand(args.slice(2));
2962
+ } else if (subcommand === "copy") {
2963
+ const name = args[2];
2964
+ if (!name) {
2965
+ console.error("Usage: directive examples copy <name>");
2966
+ process.exit(1);
2967
+ }
2968
+ await examplesCopyCommand(name, args.slice(3));
2969
+ } else {
2970
+ console.error(
2971
+ `Unknown subcommand: ${subcommand ?? "(none)"}
2972
+ Usage: ${CLI_NAME} examples list [--filter <category>]
2973
+ ${CLI_NAME} examples copy <name> [--dest <dir>]`
2974
+ );
2975
+ process.exit(1);
2976
+ }
2977
+ break;
2978
+ }
2979
+ default:
2980
+ console.error(
2981
+ `Unknown command: ${command}
2982
+ Run '${CLI_NAME} --help' for usage.`
2983
+ );
2984
+ process.exit(1);
2985
+ }
2986
+ }
2987
+ main().catch((err) => {
2988
+ console.error(err.message || err);
2989
+ process.exit(1);
2990
+ });
2991
+ //# sourceMappingURL=cli.js.map
2992
+ //# sourceMappingURL=cli.js.map