@ariaflowagents/config 0.5.3 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +93 -5
- package/dist/agent-architect/index.d.ts +10 -0
- package/dist/agent-architect/index.d.ts.map +1 -0
- package/dist/agent-architect/index.js +131 -0
- package/dist/agent-architect/index.js.map +1 -0
- package/dist/agent-architect/prompt-generator.d.ts +9 -0
- package/dist/agent-architect/prompt-generator.d.ts.map +1 -0
- package/dist/agent-architect/prompt-generator.js +155 -0
- package/dist/agent-architect/prompt-generator.js.map +1 -0
- package/dist/agent-architect/rfc-emitter.d.ts +18 -0
- package/dist/agent-architect/rfc-emitter.d.ts.map +1 -0
- package/dist/agent-architect/rfc-emitter.js +350 -0
- package/dist/agent-architect/rfc-emitter.js.map +1 -0
- package/dist/agent-architect/tool-recommender.d.ts +8 -0
- package/dist/agent-architect/tool-recommender.d.ts.map +1 -0
- package/dist/agent-architect/tool-recommender.js +87 -0
- package/dist/agent-architect/tool-recommender.js.map +1 -0
- package/dist/agent-architect/types.d.ts +904 -0
- package/dist/agent-architect/types.d.ts.map +1 -0
- package/dist/agent-architect/types.js +89 -0
- package/dist/agent-architect/types.js.map +1 -0
- package/dist/agent-architect/use-case-analyzer.d.ts +8 -0
- package/dist/agent-architect/use-case-analyzer.d.ts.map +1 -0
- package/dist/agent-architect/use-case-analyzer.js +76 -0
- package/dist/agent-architect/use-case-analyzer.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +1226 -3
- package/dist/cli.js.map +1 -1
- package/dist/flow-codegen.d.ts +6 -0
- package/dist/flow-codegen.d.ts.map +1 -0
- package/dist/flow-codegen.js +31 -0
- package/dist/flow-codegen.js.map +1 -0
- package/dist/flow-generator.d.ts +17 -0
- package/dist/flow-generator.d.ts.map +1 -0
- package/dist/flow-generator.js +202 -0
- package/dist/flow-generator.js.map +1 -0
- package/dist/index.d.ts +9 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/llm-flow-generator.d.ts +10 -0
- package/dist/llm-flow-generator.d.ts.map +1 -0
- package/dist/llm-flow-generator.js +244 -0
- package/dist/llm-flow-generator.js.map +1 -0
- package/dist/loader.d.ts +1 -1
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +175 -45
- package/dist/loader.js.map +1 -1
- package/dist/prompt-lint.d.ts +18 -0
- package/dist/prompt-lint.d.ts.map +1 -0
- package/dist/prompt-lint.js +180 -0
- package/dist/prompt-lint.js.map +1 -0
- package/dist/types.d.ts +19 -3
- package/dist/types.d.ts.map +1 -1
- package/package.json +12 -11
package/dist/cli.js
CHANGED
|
@@ -8,12 +8,22 @@
|
|
|
8
8
|
* npx ariaflow list agents|flows|tools|skills [--config <path>]
|
|
9
9
|
* npx ariaflow copy <starter> [--cwd <dir>]
|
|
10
10
|
* npx ariaflow prompt init --template <id> [--out <path>] [--cwd <dir>] [--force]
|
|
11
|
+
* npx ariaflow prompt lint [--path <file-or-dir>] [--config <path>] [--strict] [--json]
|
|
11
12
|
* npx ariaflow pack init <name> [--starter <id>] [--cwd <dir>] [--force]
|
|
13
|
+
* npx ariaflow flow generate --use-case <text> [--out <path>]
|
|
14
|
+
* npx ariaflow agent architect --use-case <text> [--mode llm|flow|hybrid]
|
|
12
15
|
*/
|
|
13
16
|
import { join, dirname, resolve, relative } from 'path';
|
|
14
17
|
import { fileURLToPath } from 'url';
|
|
15
|
-
import { existsSync, mkdirSync, copyFileSync, readdirSync, statSync, writeFileSync } from 'fs';
|
|
18
|
+
import { existsSync, mkdirSync, copyFileSync, readFileSync, readdirSync, statSync, writeFileSync } from 'fs';
|
|
19
|
+
import { createInterface } from 'node:readline/promises';
|
|
20
|
+
import { stdin as input, stdout as output } from 'node:process';
|
|
16
21
|
import { loadAriaflowConfigWithResult, printLoadSummary } from './loader.js';
|
|
22
|
+
import { generateFlowTemplate } from './flow-generator.js';
|
|
23
|
+
import { generateFlowTemplateWithLLM } from './llm-flow-generator.js';
|
|
24
|
+
import { emitFlowTypeScriptModule } from './flow-codegen.js';
|
|
25
|
+
import { lintPrompts } from './prompt-lint.js';
|
|
26
|
+
import { runArchitect } from './agent-architect/index.js';
|
|
17
27
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
18
28
|
const AVAILABLE_STARTERS = ['basic', 'sales', 'support', 'bank-hybrid'];
|
|
19
29
|
const AVAILABLE_PACK_STARTERS = ['basic'];
|
|
@@ -47,6 +57,10 @@ async function main() {
|
|
|
47
57
|
await cmdPromptInit(args.slice(2));
|
|
48
58
|
process.exit(0);
|
|
49
59
|
}
|
|
60
|
+
if (subcommand === 'lint') {
|
|
61
|
+
await cmdPromptLint(args.slice(2));
|
|
62
|
+
process.exit(0);
|
|
63
|
+
}
|
|
50
64
|
printPromptHelp();
|
|
51
65
|
process.exit(0);
|
|
52
66
|
}
|
|
@@ -60,6 +74,26 @@ async function main() {
|
|
|
60
74
|
printPackHelp();
|
|
61
75
|
process.exit(0);
|
|
62
76
|
}
|
|
77
|
+
// Flow command - generation/templating, doesn't need config
|
|
78
|
+
if (command === 'flow') {
|
|
79
|
+
const subcommand = args[1] ?? 'help';
|
|
80
|
+
if (subcommand === 'generate') {
|
|
81
|
+
await cmdFlowGenerate(args.slice(2));
|
|
82
|
+
process.exit(0);
|
|
83
|
+
}
|
|
84
|
+
printFlowHelp();
|
|
85
|
+
process.exit(0);
|
|
86
|
+
}
|
|
87
|
+
// Agent command - orchestrated generation, doesn't need config
|
|
88
|
+
if (command === 'agent') {
|
|
89
|
+
const subcommand = args[1] ?? 'help';
|
|
90
|
+
if (subcommand === 'architect') {
|
|
91
|
+
await cmdAgentArchitect(args.slice(2));
|
|
92
|
+
process.exit(0);
|
|
93
|
+
}
|
|
94
|
+
printAgentHelp();
|
|
95
|
+
process.exit(0);
|
|
96
|
+
}
|
|
63
97
|
const options = {
|
|
64
98
|
config: process.env.ARIAFLOW_CONFIG,
|
|
65
99
|
};
|
|
@@ -336,6 +370,9 @@ Commands:
|
|
|
336
370
|
copy <starter> Copy a starter template to your project
|
|
337
371
|
pack init Create a pack from a starter template
|
|
338
372
|
prompt init Create a markdown prompt template file
|
|
373
|
+
prompt lint Lint prompt files with Aria quality gates
|
|
374
|
+
flow generate Generate a production-style flow JSON scaffold
|
|
375
|
+
agent architect Generate blueprint + prompt/flow/code artifacts
|
|
339
376
|
help Show this help
|
|
340
377
|
|
|
341
378
|
Options:
|
|
@@ -351,6 +388,9 @@ Examples:
|
|
|
351
388
|
ariaflow copy sales
|
|
352
389
|
ariaflow pack init my-pack --starter basic
|
|
353
390
|
ariaflow prompt init --template support-agent
|
|
391
|
+
ariaflow prompt lint --strict
|
|
392
|
+
ariaflow flow generate --use-case "medical appointment scheduling" --out ./.ariaflow/flow/appointments.json
|
|
393
|
+
ariaflow agent architect --use-case "hospital support triage" --mode hybrid --target both
|
|
354
394
|
`);
|
|
355
395
|
}
|
|
356
396
|
async function cmdPackInit(args) {
|
|
@@ -530,22 +570,1205 @@ async function cmdPromptInit(args) {
|
|
|
530
570
|
}
|
|
531
571
|
function printPromptHelp() {
|
|
532
572
|
console.log(`
|
|
533
|
-
AriaFlow Prompt
|
|
573
|
+
AriaFlow Prompt Commands
|
|
534
574
|
|
|
535
575
|
Usage:
|
|
536
576
|
ariaflow prompt init --template <id> [--out <path>] [--cwd <dir>] [--force]
|
|
577
|
+
ariaflow prompt lint [--path <file-or-dir>] [--config <path>] [--cwd <dir>] [--strict] [--json]
|
|
537
578
|
|
|
538
|
-
|
|
579
|
+
Subcommands:
|
|
580
|
+
init Create a markdown prompt template with standard sections
|
|
581
|
+
lint Lint prompts against Aria project quality gates
|
|
539
582
|
|
|
540
583
|
Options:
|
|
541
584
|
--template <id> Template id and filename (required)
|
|
542
585
|
--out <path> Output file path (default: ./.ariaflow/prompts/<id>.md)
|
|
543
586
|
--cwd <dir> Base directory (default: current directory)
|
|
544
587
|
--force Overwrite if file exists
|
|
588
|
+
--path <target> Lint specific file or directory (default: ./.ariaflow/prompts)
|
|
589
|
+
--config <path> Config path for agent-type-aware linting (default: auto-detect)
|
|
590
|
+
--strict Treat warnings as failures (non-zero exit)
|
|
591
|
+
--json Output machine-readable JSON report
|
|
545
592
|
|
|
546
593
|
Examples:
|
|
547
594
|
ariaflow prompt init --template support-agent
|
|
548
595
|
ariaflow prompt init --template triage-agent --out .ariaflow/prompts/triage.md
|
|
596
|
+
ariaflow prompt lint
|
|
597
|
+
ariaflow prompt lint --path .ariaflow/prompts/triage.md --strict
|
|
598
|
+
`);
|
|
599
|
+
}
|
|
600
|
+
function issueSort(a, b) {
|
|
601
|
+
if (a.file !== b.file)
|
|
602
|
+
return a.file.localeCompare(b.file);
|
|
603
|
+
if (a.severity !== b.severity)
|
|
604
|
+
return a.severity === 'error' ? -1 : 1;
|
|
605
|
+
return a.ruleId.localeCompare(b.ruleId);
|
|
606
|
+
}
|
|
607
|
+
async function cmdPromptLint(args) {
|
|
608
|
+
let cwd = process.cwd();
|
|
609
|
+
let pathArg;
|
|
610
|
+
let configPath;
|
|
611
|
+
let strict = false;
|
|
612
|
+
let json = false;
|
|
613
|
+
for (let i = 0; i < args.length; i++) {
|
|
614
|
+
const arg = args[i];
|
|
615
|
+
if (arg === '--cwd' && i + 1 < args.length) {
|
|
616
|
+
cwd = resolve(args[i + 1]);
|
|
617
|
+
i++;
|
|
618
|
+
continue;
|
|
619
|
+
}
|
|
620
|
+
if (arg === '--path' && i + 1 < args.length) {
|
|
621
|
+
pathArg = args[i + 1];
|
|
622
|
+
i++;
|
|
623
|
+
continue;
|
|
624
|
+
}
|
|
625
|
+
if (arg === '--config' && i + 1 < args.length) {
|
|
626
|
+
configPath = args[i + 1];
|
|
627
|
+
i++;
|
|
628
|
+
continue;
|
|
629
|
+
}
|
|
630
|
+
if (arg === '--strict') {
|
|
631
|
+
strict = true;
|
|
632
|
+
continue;
|
|
633
|
+
}
|
|
634
|
+
if (arg === '--json') {
|
|
635
|
+
json = true;
|
|
636
|
+
continue;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
const result = lintPrompts({
|
|
640
|
+
cwd,
|
|
641
|
+
path: pathArg,
|
|
642
|
+
configPath,
|
|
643
|
+
});
|
|
644
|
+
const sortedIssues = [...result.issues].sort(issueSort);
|
|
645
|
+
const errors = sortedIssues.filter(issue => issue.severity === 'error');
|
|
646
|
+
const warnings = sortedIssues.filter(issue => issue.severity === 'warn');
|
|
647
|
+
const shouldFail = errors.length > 0 || (strict && warnings.length > 0);
|
|
648
|
+
if (json) {
|
|
649
|
+
process.stdout.write(JSON.stringify({
|
|
650
|
+
filesScanned: result.filesScanned,
|
|
651
|
+
issueCount: sortedIssues.length,
|
|
652
|
+
errors: errors.length,
|
|
653
|
+
warnings: warnings.length,
|
|
654
|
+
strict,
|
|
655
|
+
passed: !shouldFail,
|
|
656
|
+
issues: sortedIssues,
|
|
657
|
+
}, null, 2) + '\n');
|
|
658
|
+
process.exit(shouldFail ? 1 : 0);
|
|
659
|
+
}
|
|
660
|
+
console.log('\nAriaFlow Prompt Lint\n');
|
|
661
|
+
console.log(`Scanned files: ${result.filesScanned.length}`);
|
|
662
|
+
console.log(`Errors: ${errors.length}`);
|
|
663
|
+
console.log(`Warnings: ${warnings.length}`);
|
|
664
|
+
if (strict) {
|
|
665
|
+
console.log('Mode: strict (warnings fail)');
|
|
666
|
+
}
|
|
667
|
+
if (sortedIssues.length === 0) {
|
|
668
|
+
console.log('\n✅ No issues found.\n');
|
|
669
|
+
process.exit(0);
|
|
670
|
+
}
|
|
671
|
+
console.log('\nIssues:\n');
|
|
672
|
+
for (const issue of sortedIssues) {
|
|
673
|
+
const icon = issue.severity === 'error' ? '❌' : '⚠️';
|
|
674
|
+
const relFile = issue.file.startsWith(cwd) ? `.${issue.file.slice(cwd.length)}` : issue.file;
|
|
675
|
+
console.log(`${icon} [${issue.severity}] ${issue.ruleId}`);
|
|
676
|
+
console.log(` ${issue.message}`);
|
|
677
|
+
console.log(` File: ${relFile}\n`);
|
|
678
|
+
}
|
|
679
|
+
if (shouldFail) {
|
|
680
|
+
console.log('❌ Prompt lint failed.\n');
|
|
681
|
+
process.exit(1);
|
|
682
|
+
}
|
|
683
|
+
console.log('✅ Prompt lint passed with warnings.\n');
|
|
684
|
+
}
|
|
685
|
+
function toSlug(value) {
|
|
686
|
+
return value
|
|
687
|
+
.toLowerCase()
|
|
688
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
689
|
+
.replace(/^-+|-+$/g, '')
|
|
690
|
+
.slice(0, 64) || 'generated-agent';
|
|
691
|
+
}
|
|
692
|
+
function toTsConstCase(value) {
|
|
693
|
+
const clean = value.replace(/[^a-zA-Z0-9]+/g, ' ').trim();
|
|
694
|
+
const words = clean.split(/\s+/).filter(Boolean);
|
|
695
|
+
if (words.length === 0)
|
|
696
|
+
return 'generatedAgent';
|
|
697
|
+
const [head, ...tail] = words;
|
|
698
|
+
return `${head.toLowerCase()}${tail.map(word => word[0].toUpperCase() + word.slice(1).toLowerCase()).join('')}`;
|
|
699
|
+
}
|
|
700
|
+
function parseCsv(value) {
|
|
701
|
+
return value
|
|
702
|
+
.split(',')
|
|
703
|
+
.map(item => item.trim())
|
|
704
|
+
.filter(Boolean);
|
|
705
|
+
}
|
|
706
|
+
function uniqueStrings(values) {
|
|
707
|
+
return Array.from(new Set(values.map(value => value.trim()).filter(Boolean)));
|
|
708
|
+
}
|
|
709
|
+
function ensureWritableFile(target, content, force) {
|
|
710
|
+
if (existsSync(target) && !force) {
|
|
711
|
+
throw new Error(`File already exists: ${target}. Use --force to overwrite.`);
|
|
712
|
+
}
|
|
713
|
+
const parentDir = dirname(target);
|
|
714
|
+
if (!existsSync(parentDir)) {
|
|
715
|
+
mkdirSync(parentDir, { recursive: true });
|
|
716
|
+
}
|
|
717
|
+
writeFileSync(target, content, 'utf8');
|
|
718
|
+
}
|
|
719
|
+
function inferDomainSafetyAddendum(useCase) {
|
|
720
|
+
const lower = useCase.toLowerCase();
|
|
721
|
+
if (/(medical|health|hospital|clinic|patient|appointment)/.test(lower)) {
|
|
722
|
+
return 'Treat health and personal data as sensitive. Collect only minimum required details, avoid exposing full identifiers, and do not provide medical diagnosis. If symptoms indicate emergency risk, instruct immediate emergency care and escalate.';
|
|
723
|
+
}
|
|
724
|
+
if (/(bank|billing|payment|finance|loan|refund|invoice)/.test(lower)) {
|
|
725
|
+
return 'Treat financial data as sensitive. Never request full card or account secrets in plain text. Confirm all money movement actions explicitly before execution.';
|
|
726
|
+
}
|
|
727
|
+
if (/(legal|law|contract|compliance)/.test(lower)) {
|
|
728
|
+
return 'Do not present legal conclusions as professional legal advice. State uncertainty clearly and recommend qualified human review for high-impact legal decisions.';
|
|
729
|
+
}
|
|
730
|
+
return '';
|
|
731
|
+
}
|
|
732
|
+
function normalizeGeneratedText(text) {
|
|
733
|
+
if (!text || text.trim().length === 0) {
|
|
734
|
+
return text;
|
|
735
|
+
}
|
|
736
|
+
const compactSpaces = text
|
|
737
|
+
.replace(/\r\n/g, '\n')
|
|
738
|
+
.replace(/\.\./g, '.')
|
|
739
|
+
.replace(/[ \t]{2,}/g, ' ')
|
|
740
|
+
.replace(/We are getting (inbound|outbound) calls for/gi, 'You are handling $1 calls for')
|
|
741
|
+
.replace(/:\s+\./g, ':');
|
|
742
|
+
const withSectionBreaks = compactSpaces
|
|
743
|
+
.replace(/([.!?])\s+(##?\s+)/g, '$1\n\n$2')
|
|
744
|
+
.replace(/\n{3,}/g, '\n\n');
|
|
745
|
+
return withSectionBreaks
|
|
746
|
+
.split('\n')
|
|
747
|
+
.map(line => line.trimEnd())
|
|
748
|
+
.join('\n')
|
|
749
|
+
.trim();
|
|
750
|
+
}
|
|
751
|
+
function normalizeFlowDraft(flow) {
|
|
752
|
+
return {
|
|
753
|
+
...flow,
|
|
754
|
+
defaultRolePrompt: flow.defaultRolePrompt ? normalizeGeneratedText(flow.defaultRolePrompt) : flow.defaultRolePrompt,
|
|
755
|
+
nodes: flow.nodes.map(node => ({
|
|
756
|
+
...node,
|
|
757
|
+
prompt: typeof node.prompt === 'string' ? normalizeGeneratedText(node.prompt) : node.prompt,
|
|
758
|
+
})),
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
function addTransitionGuidance(flow) {
|
|
762
|
+
if (!flow.transitions || flow.transitions.length === 0) {
|
|
763
|
+
return flow;
|
|
764
|
+
}
|
|
765
|
+
const transitionsByFrom = new Map();
|
|
766
|
+
for (const transition of flow.transitions) {
|
|
767
|
+
if (!transition.from || !transition.on) {
|
|
768
|
+
continue;
|
|
769
|
+
}
|
|
770
|
+
const list = transitionsByFrom.get(transition.from) ?? [];
|
|
771
|
+
list.push(transition.on);
|
|
772
|
+
transitionsByFrom.set(transition.from, list);
|
|
773
|
+
}
|
|
774
|
+
return {
|
|
775
|
+
...flow,
|
|
776
|
+
nodes: flow.nodes.map(node => {
|
|
777
|
+
const options = transitionsByFrom.get(node.id) ?? [];
|
|
778
|
+
if (options.length === 0 || typeof node.prompt !== 'string') {
|
|
779
|
+
return node;
|
|
780
|
+
}
|
|
781
|
+
const uniqueOptions = uniqueStrings(options);
|
|
782
|
+
const guidance = [
|
|
783
|
+
'',
|
|
784
|
+
'## Transition Emission',
|
|
785
|
+
`When this node is complete, emit exactly one transition event from: ${uniqueOptions.join(', ')}.`,
|
|
786
|
+
'Do not emit transition events before required data is confirmed.',
|
|
787
|
+
].join('\n');
|
|
788
|
+
return {
|
|
789
|
+
...node,
|
|
790
|
+
prompt: `${node.prompt}${guidance}`,
|
|
791
|
+
};
|
|
792
|
+
}),
|
|
793
|
+
};
|
|
794
|
+
}
|
|
795
|
+
function inferSuggestedToolSlots(useCase) {
|
|
796
|
+
const lower = useCase.toLowerCase();
|
|
797
|
+
if (/(medical|health|hospital|clinic|patient|appointment)/.test(lower)) {
|
|
798
|
+
return [
|
|
799
|
+
'verify_patient_identity',
|
|
800
|
+
'search_appointment_slots',
|
|
801
|
+
'book_appointment',
|
|
802
|
+
'reschedule_appointment',
|
|
803
|
+
'cancel_appointment',
|
|
804
|
+
'escalate_to_clinical_triage',
|
|
805
|
+
];
|
|
806
|
+
}
|
|
807
|
+
if (/(billing|payment|refund|invoice|subscription|finance|bank|loan)/.test(lower)) {
|
|
808
|
+
return [
|
|
809
|
+
'lookup_account',
|
|
810
|
+
'get_invoice_status',
|
|
811
|
+
'create_payment_intent',
|
|
812
|
+
'issue_refund',
|
|
813
|
+
'escalate_to_billing_specialist',
|
|
814
|
+
];
|
|
815
|
+
}
|
|
816
|
+
if (/(support|ticket|incident|helpdesk|customer support)/.test(lower)) {
|
|
817
|
+
return [
|
|
818
|
+
'lookup_customer_profile',
|
|
819
|
+
'search_knowledge_base',
|
|
820
|
+
'create_support_ticket',
|
|
821
|
+
'update_ticket_status',
|
|
822
|
+
'escalate_to_human_agent',
|
|
823
|
+
];
|
|
824
|
+
}
|
|
825
|
+
if (/(sales|lead|qualification|demo|crm)/.test(lower)) {
|
|
826
|
+
return [
|
|
827
|
+
'lookup_company_profile',
|
|
828
|
+
'qualify_lead',
|
|
829
|
+
'create_crm_record',
|
|
830
|
+
'schedule_demo',
|
|
831
|
+
'handoff_to_account_exec',
|
|
832
|
+
];
|
|
833
|
+
}
|
|
834
|
+
return ['lookup_context', 'execute_action', 'escalate_to_human'];
|
|
835
|
+
}
|
|
836
|
+
function addToolGuidanceToFlow(flow, selectedTools, suggestedToolSlots) {
|
|
837
|
+
const toolLine = selectedTools.length > 0
|
|
838
|
+
? `Available tools for verification/execution: ${selectedTools.join(', ')}.`
|
|
839
|
+
: 'No tools are configured yet; do not claim tool-based verification. Ask for clarification or escalate when verification is required.';
|
|
840
|
+
const suggestionLine = selectedTools.length === 0 && suggestedToolSlots.length > 0
|
|
841
|
+
? `Suggested tool slots to configure for this use case: ${suggestedToolSlots.join(', ')}.`
|
|
842
|
+
: undefined;
|
|
843
|
+
const existing = flow.defaultRolePrompt ?? '';
|
|
844
|
+
const lines = ['## Tool Availability', `- ${toolLine}`];
|
|
845
|
+
if (suggestionLine) {
|
|
846
|
+
lines.push(`- ${suggestionLine}`);
|
|
847
|
+
}
|
|
848
|
+
const section = `${existing}\n\n${lines.join('\n')}`;
|
|
849
|
+
return {
|
|
850
|
+
...flow,
|
|
851
|
+
defaultRolePrompt: section.trim(),
|
|
852
|
+
};
|
|
853
|
+
}
|
|
854
|
+
async function discoverToolCandidates(cwd) {
|
|
855
|
+
const discovered = [];
|
|
856
|
+
try {
|
|
857
|
+
const configPath = existsSync(join(cwd, 'ariaflow.jsonc'))
|
|
858
|
+
? join(cwd, 'ariaflow.jsonc')
|
|
859
|
+
: existsSync(join(cwd, 'ariaflow.json'))
|
|
860
|
+
? join(cwd, 'ariaflow.json')
|
|
861
|
+
: undefined;
|
|
862
|
+
if (configPath) {
|
|
863
|
+
const loaded = await loadAriaflowConfigWithResult({
|
|
864
|
+
configPath,
|
|
865
|
+
silent: true,
|
|
866
|
+
});
|
|
867
|
+
discovered.push(...Object.keys(loaded.config.tools));
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
catch {
|
|
871
|
+
// Best-effort discovery only
|
|
872
|
+
}
|
|
873
|
+
const toolsDir = join(cwd, '.ariaflow', 'tools');
|
|
874
|
+
if (existsSync(toolsDir)) {
|
|
875
|
+
try {
|
|
876
|
+
for (const entry of readdirSync(toolsDir, { withFileTypes: true })) {
|
|
877
|
+
if (!entry.isDirectory())
|
|
878
|
+
continue;
|
|
879
|
+
const toolJson = join(toolsDir, entry.name, 'tool.json');
|
|
880
|
+
if (existsSync(toolJson)) {
|
|
881
|
+
discovered.push(entry.name);
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
catch {
|
|
886
|
+
// Best-effort discovery only
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
return uniqueStrings(discovered);
|
|
890
|
+
}
|
|
891
|
+
function buildPromptLayers(useCase, mode, selectedTools, suggestedToolSlots, clarifications) {
|
|
892
|
+
const toolList = selectedTools.length > 0 ? selectedTools.join(', ') : 'None configured';
|
|
893
|
+
const toolSuggestions = selectedTools.length === 0 && suggestedToolSlots.length > 0
|
|
894
|
+
? ` Suggested tool slots: ${suggestedToolSlots.join(', ')}.`
|
|
895
|
+
: '';
|
|
896
|
+
const domainSafety = inferDomainSafetyAddendum(useCase);
|
|
897
|
+
const constraints = clarifications
|
|
898
|
+
.filter(item => /constraint|never do|compliance|safety/i.test(item.question))
|
|
899
|
+
.map(item => item.answer)
|
|
900
|
+
.join(' | ');
|
|
901
|
+
return {
|
|
902
|
+
identityAndScope: `You are an AriaFlow agent focused on this use case: ${useCase}. Prioritize task completion, clear user outcomes, and deterministic behavior for mode ${mode}.`,
|
|
903
|
+
safetyAndGuardrails: `Do not guess or invent facts. If uncertain, ask a clarifying question. Escalate or refuse unsafe requests. ${domainSafety ? `${domainSafety} ` : ''}${constraints ? `Additional constraints: ${constraints}.` : ''}`.trim(),
|
|
904
|
+
toolContract: `Use available tools to verify facts before answering. Selected tools: ${toolList}.${toolSuggestions} ${selectedTools.length > 0 ? 'Call tools when external verification or side effects are required.' : 'No tools are configured; do not claim tool-grounded verification until real tools are wired.'} If a tool fails, explain the limitation and propose the next safe step.`,
|
|
905
|
+
reasoningWorkflow: 'Ask one focused question at a time. Confirm critical fields before side effects. Do not mix tool transitions and user-facing prose in the same step.',
|
|
906
|
+
executionPolicy: mode === 'llm'
|
|
907
|
+
? 'Operate as an LLM-first assistant with explicit tool checks before factual claims.'
|
|
908
|
+
: 'Operate as a flow-aware assistant. Keep SOP in nodes/transitions and enforce transition contracts.',
|
|
909
|
+
outputVerification: 'Keep responses concise, policy-compliant, and verifiable. Summarize decisions and ask for confirmation before final actions.',
|
|
910
|
+
};
|
|
911
|
+
}
|
|
912
|
+
function buildLayeredPromptMarkdown(agentName, layers) {
|
|
913
|
+
return [
|
|
914
|
+
`# ${agentName} System Prompt`,
|
|
915
|
+
'',
|
|
916
|
+
'## Identity and Scope',
|
|
917
|
+
layers.identityAndScope,
|
|
918
|
+
'',
|
|
919
|
+
'## Safety and Guardrails',
|
|
920
|
+
layers.safetyAndGuardrails,
|
|
921
|
+
'',
|
|
922
|
+
'## Tool Contract',
|
|
923
|
+
layers.toolContract,
|
|
924
|
+
'',
|
|
925
|
+
'## Reasoning Workflow',
|
|
926
|
+
layers.reasoningWorkflow,
|
|
927
|
+
'',
|
|
928
|
+
'## Execution Policy',
|
|
929
|
+
layers.executionPolicy,
|
|
930
|
+
'',
|
|
931
|
+
'## Output and Verification',
|
|
932
|
+
layers.outputVerification,
|
|
933
|
+
'',
|
|
934
|
+
].join('\n');
|
|
935
|
+
}
|
|
936
|
+
function summarizeUseCaseForPrompt(useCase) {
|
|
937
|
+
const compact = useCase.replace(/\s+/g, ' ').trim();
|
|
938
|
+
if (compact.length <= 220) {
|
|
939
|
+
return compact;
|
|
940
|
+
}
|
|
941
|
+
const slice = compact.slice(0, 220);
|
|
942
|
+
const lastSpace = slice.lastIndexOf(' ');
|
|
943
|
+
const safe = lastSpace > 120 ? slice.slice(0, lastSpace) : slice;
|
|
944
|
+
return `${safe.trim()}...`;
|
|
945
|
+
}
|
|
946
|
+
function extractFlowTaskTitles(prompt) {
|
|
947
|
+
const titles = [];
|
|
948
|
+
const pattern = /^###\s*Task\s*\d+:\s*(.+)$/gm;
|
|
949
|
+
let match;
|
|
950
|
+
while ((match = pattern.exec(prompt)) !== null) {
|
|
951
|
+
const title = (match[1] ?? '').trim();
|
|
952
|
+
if (title.length > 0) {
|
|
953
|
+
titles.push(title);
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
return titles;
|
|
957
|
+
}
|
|
958
|
+
function extractSuccessCriteria(prompt) {
|
|
959
|
+
const marker = '# Success criteria WHEN you can move to next step';
|
|
960
|
+
const idx = prompt.indexOf(marker);
|
|
961
|
+
if (idx < 0) {
|
|
962
|
+
return [];
|
|
963
|
+
}
|
|
964
|
+
const tail = prompt.slice(idx + marker.length);
|
|
965
|
+
const lines = tail
|
|
966
|
+
.split('\n')
|
|
967
|
+
.map(line => line.trim())
|
|
968
|
+
.filter(line => line.startsWith('- '))
|
|
969
|
+
.map(line => line.slice(2).trim())
|
|
970
|
+
.filter(Boolean);
|
|
971
|
+
return uniqueStrings(lines);
|
|
972
|
+
}
|
|
973
|
+
function buildFlowBackedPromptMarkdown(agentName, useCase, layers, flow, selectedTools) {
|
|
974
|
+
const mainAgendaNode = flow.nodes.find(node => node.id === 'main_agenda')
|
|
975
|
+
?? flow.nodes.find(node => node.name?.toLowerCase().includes('agenda'));
|
|
976
|
+
const transitionLines = (flow.transitions ?? []).map(transition => {
|
|
977
|
+
const label = transition.contract?.label?.trim() || `${transition.from} -> ${transition.to}`;
|
|
978
|
+
const event = transition.on ? `event=\`${transition.on}\`` : 'event=`<none>`';
|
|
979
|
+
const condition = transition.contract?.conditionText?.trim() || 'No explicit condition text.';
|
|
980
|
+
return `- ${label} (${event}): ${condition}`;
|
|
981
|
+
});
|
|
982
|
+
const toolLines = selectedTools.length > 0
|
|
983
|
+
? selectedTools.map(tool => `- \`${tool}\``)
|
|
984
|
+
: ['- No concrete tools configured yet.'];
|
|
985
|
+
const mainAgendaPrompt = typeof mainAgendaNode?.prompt === 'string' ? mainAgendaNode.prompt : '';
|
|
986
|
+
const focusAreas = extractFlowTaskTitles(mainAgendaPrompt);
|
|
987
|
+
const successCriteria = extractSuccessCriteria(mainAgendaPrompt);
|
|
988
|
+
return [
|
|
989
|
+
`# ${agentName} System Prompt`,
|
|
990
|
+
'',
|
|
991
|
+
'## Mission',
|
|
992
|
+
summarizeUseCaseForPrompt(useCase),
|
|
993
|
+
'',
|
|
994
|
+
'## LLM Global Policy (Flow Derived)',
|
|
995
|
+
flow.defaultRolePrompt?.trim() || layers.identityAndScope,
|
|
996
|
+
'',
|
|
997
|
+
'## Conversation Focus Areas',
|
|
998
|
+
focusAreas.length > 0
|
|
999
|
+
? focusAreas.map(item => `- ${item}`).join('\n')
|
|
1000
|
+
: '- Collect intent, key details, and confirmation in deterministic sequence.',
|
|
1001
|
+
'',
|
|
1002
|
+
'## Completion Criteria',
|
|
1003
|
+
successCriteria.length > 0
|
|
1004
|
+
? successCriteria.slice(0, 8).map(item => `- ${item}`).join('\n')
|
|
1005
|
+
: '- Confirm required details before any side effects.',
|
|
1006
|
+
'',
|
|
1007
|
+
'## Tooling',
|
|
1008
|
+
toolLines.join('\n'),
|
|
1009
|
+
'',
|
|
1010
|
+
'## Tool Contract',
|
|
1011
|
+
layers.toolContract,
|
|
1012
|
+
'',
|
|
1013
|
+
'## Transition Contracts',
|
|
1014
|
+
transitionLines.length > 0
|
|
1015
|
+
? transitionLines.join('\n')
|
|
1016
|
+
: '- No transitions configured.',
|
|
1017
|
+
'',
|
|
1018
|
+
'## Safety and Guardrails',
|
|
1019
|
+
layers.safetyAndGuardrails,
|
|
1020
|
+
'',
|
|
1021
|
+
'## Output and Verification',
|
|
1022
|
+
layers.outputVerification,
|
|
1023
|
+
'',
|
|
1024
|
+
].join('\n');
|
|
1025
|
+
}
|
|
1026
|
+
function buildPromptTypeScriptModule(constName, prompt) {
|
|
1027
|
+
const serialized = JSON.stringify(prompt);
|
|
1028
|
+
return `export const ${constName}SystemPrompt = ${serialized};\n\nexport default ${constName}SystemPrompt;\n`;
|
|
1029
|
+
}
|
|
1030
|
+
function buildBlueprintRfcMarkdown(title, blueprint, outputFiles) {
|
|
1031
|
+
return [
|
|
1032
|
+
`# RFC: ${title}`,
|
|
1033
|
+
'',
|
|
1034
|
+
`- Generated: ${blueprint.generationMeta.generatedAt}`,
|
|
1035
|
+
`- Use case: ${blueprint.useCase}`,
|
|
1036
|
+
`- Mode: ${blueprint.mode}`,
|
|
1037
|
+
`- Target: ${blueprint.target}`,
|
|
1038
|
+
'',
|
|
1039
|
+
'## Selected Tools',
|
|
1040
|
+
blueprint.selectedTools.length > 0 ? blueprint.selectedTools.map(tool => `- ${tool}`).join('\n') : '- None declared',
|
|
1041
|
+
'',
|
|
1042
|
+
'## Suggested Tool Slots',
|
|
1043
|
+
blueprint.suggestedToolSlots.length > 0
|
|
1044
|
+
? blueprint.suggestedToolSlots.map(tool => `- ${tool}`).join('\n')
|
|
1045
|
+
: '- None suggested',
|
|
1046
|
+
'',
|
|
1047
|
+
'## Prompt Layers',
|
|
1048
|
+
Object.entries(blueprint.promptLayers).map(([key, value]) => `### ${key}\n${value}`).join('\n\n'),
|
|
1049
|
+
'',
|
|
1050
|
+
'## Generated Artifacts',
|
|
1051
|
+
outputFiles.map(file => `- ${file}`).join('\n'),
|
|
1052
|
+
'',
|
|
1053
|
+
'## Eval Scenarios',
|
|
1054
|
+
blueprint.evalPlan.scenarios.map(item => `- ${item}`).join('\n'),
|
|
1055
|
+
'',
|
|
1056
|
+
].join('\n');
|
|
1057
|
+
}
|
|
1058
|
+
async function collectClarifications(maxQuestions) {
|
|
1059
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
1060
|
+
return [];
|
|
1061
|
+
}
|
|
1062
|
+
const questions = [
|
|
1063
|
+
'Who are the primary end users for this agent?',
|
|
1064
|
+
'What are the top outcomes this agent must achieve?',
|
|
1065
|
+
'List required tools or capabilities (comma-separated, optional):',
|
|
1066
|
+
'Any hard safety or compliance constraints?',
|
|
1067
|
+
'What should this agent never do?',
|
|
1068
|
+
];
|
|
1069
|
+
const rl = createInterface({ input, output });
|
|
1070
|
+
const answers = [];
|
|
1071
|
+
const budget = Math.max(1, Math.min(maxQuestions, 6));
|
|
1072
|
+
try {
|
|
1073
|
+
for (let i = 0; i < budget && i < questions.length; i++) {
|
|
1074
|
+
const question = questions[i];
|
|
1075
|
+
const response = (await rl.question(`${question}\n> `)).trim();
|
|
1076
|
+
if (response.length > 0) {
|
|
1077
|
+
answers.push({ question, answer: response });
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
finally {
|
|
1082
|
+
rl.close();
|
|
1083
|
+
}
|
|
1084
|
+
return answers;
|
|
1085
|
+
}
|
|
1086
|
+
function printAgentHelp() {
|
|
1087
|
+
console.log(`
|
|
1088
|
+
AriaFlow Agent Architect
|
|
1089
|
+
|
|
1090
|
+
Usage:
|
|
1091
|
+
ariaflow agent architect --use-case <text> [options]
|
|
1092
|
+
|
|
1093
|
+
Generates:
|
|
1094
|
+
- AgentBlueprint JSON
|
|
1095
|
+
- Layered prompt artifacts
|
|
1096
|
+
- Flow artifacts (for flow/hybrid modes)
|
|
1097
|
+
- Typed code artifacts (for code/both targets)
|
|
1098
|
+
- RFC with implementation instructions
|
|
1099
|
+
|
|
1100
|
+
Options:
|
|
1101
|
+
--use-case <text> Use-case description (required)
|
|
1102
|
+
--name <id> Output id override (default: slugified use-case)
|
|
1103
|
+
--mode <value> llm | flow | hybrid (default: hybrid)
|
|
1104
|
+
--target <value> config | code | both (default: both)
|
|
1105
|
+
--model <id> Model id for LLM generation (default: gpt-5-mini)
|
|
1106
|
+
--llm-driven Use new LLM-driven analysis (analyzes use case, recommends tools, generates 6-layer prompts)
|
|
1107
|
+
--detail-level <value> simple | detailed (default: detailed) - simple shows only required tools
|
|
1108
|
+
--direction <value> inbound | outbound (default: inbound)
|
|
1109
|
+
--agent-name <name> Agent display name used in flow generation (default: Sam)
|
|
1110
|
+
--context-file <path> Additional context file (MD/TXT/YAML)
|
|
1111
|
+
--timeout-ms <n> LLM generation timeout in ms (default: 120000)
|
|
1112
|
+
--interactive Enable bounded follow-up clarification loop
|
|
1113
|
+
--emit-rfc Emit a generated RFC markdown file
|
|
1114
|
+
--out-dir <path> Output directory root (default: ./.ariaflow/generated)
|
|
1115
|
+
--cwd <dir> Base directory (default: current directory)
|
|
1116
|
+
--force Overwrite existing output files
|
|
1117
|
+
|
|
1118
|
+
Examples:
|
|
1119
|
+
ariaflow agent architect --use-case "cake shop order taking" --llm-driven --detail-level simple
|
|
1120
|
+
ariaflow agent architect --use-case "bakery inquiry agent" --llm-driven --mode flow
|
|
1121
|
+
ariaflow agent architect --use-case "medical form intake" --llm-driven --emit-rfc
|
|
1122
|
+
`);
|
|
1123
|
+
}
|
|
1124
|
+
async function cmdAgentArchitect(args) {
|
|
1125
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
1126
|
+
printAgentHelp();
|
|
1127
|
+
return;
|
|
1128
|
+
}
|
|
1129
|
+
let useCase;
|
|
1130
|
+
let mode = 'hybrid';
|
|
1131
|
+
let target = 'both';
|
|
1132
|
+
let name;
|
|
1133
|
+
let tools = [];
|
|
1134
|
+
let model = 'gpt-5-mini';
|
|
1135
|
+
let llmMode = 'hybrid';
|
|
1136
|
+
let allowFlowFallback = false;
|
|
1137
|
+
let direction = 'inbound';
|
|
1138
|
+
let agentName = 'Sam';
|
|
1139
|
+
let contextFile;
|
|
1140
|
+
let timeoutMs;
|
|
1141
|
+
let interactive = false;
|
|
1142
|
+
let maxQuestions = 4;
|
|
1143
|
+
let emitRfc = false;
|
|
1144
|
+
let llmDriven = false;
|
|
1145
|
+
let detailLevel = 'detailed';
|
|
1146
|
+
let outDir = '.ariaflow/generated';
|
|
1147
|
+
let cwd = process.cwd();
|
|
1148
|
+
let force = false;
|
|
1149
|
+
for (let i = 0; i < args.length; i++) {
|
|
1150
|
+
const arg = args[i];
|
|
1151
|
+
if (arg === '--use-case' && i + 1 < args.length) {
|
|
1152
|
+
useCase = args[i + 1];
|
|
1153
|
+
i++;
|
|
1154
|
+
continue;
|
|
1155
|
+
}
|
|
1156
|
+
if (arg === '--name' && i + 1 < args.length) {
|
|
1157
|
+
name = args[i + 1];
|
|
1158
|
+
i++;
|
|
1159
|
+
continue;
|
|
1160
|
+
}
|
|
1161
|
+
if (arg === '--mode' && i + 1 < args.length) {
|
|
1162
|
+
const value = (args[i + 1] ?? '').trim().toLowerCase();
|
|
1163
|
+
if (value === 'llm' || value === 'flow' || value === 'hybrid') {
|
|
1164
|
+
mode = value;
|
|
1165
|
+
}
|
|
1166
|
+
else {
|
|
1167
|
+
console.error(`\n❌ Invalid --mode value: ${args[i + 1]}`);
|
|
1168
|
+
console.log('Allowed values: llm, flow, hybrid\n');
|
|
1169
|
+
process.exit(1);
|
|
1170
|
+
}
|
|
1171
|
+
i++;
|
|
1172
|
+
continue;
|
|
1173
|
+
}
|
|
1174
|
+
if (arg === '--target' && i + 1 < args.length) {
|
|
1175
|
+
const value = (args[i + 1] ?? '').trim().toLowerCase();
|
|
1176
|
+
if (value === 'config' || value === 'code' || value === 'both') {
|
|
1177
|
+
target = value;
|
|
1178
|
+
}
|
|
1179
|
+
else {
|
|
1180
|
+
console.error(`\n❌ Invalid --target value: ${args[i + 1]}`);
|
|
1181
|
+
console.log('Allowed values: config, code, both\n');
|
|
1182
|
+
process.exit(1);
|
|
1183
|
+
}
|
|
1184
|
+
i++;
|
|
1185
|
+
continue;
|
|
1186
|
+
}
|
|
1187
|
+
if (arg === '--tools' && i + 1 < args.length) {
|
|
1188
|
+
tools = uniqueStrings([...tools, ...parseCsv(args[i + 1] ?? '')]);
|
|
1189
|
+
i++;
|
|
1190
|
+
continue;
|
|
1191
|
+
}
|
|
1192
|
+
if (arg === '--llm') {
|
|
1193
|
+
// Backward-compatible no-op: architect is LLM-required.
|
|
1194
|
+
continue;
|
|
1195
|
+
}
|
|
1196
|
+
if (arg === '--model' && i + 1 < args.length) {
|
|
1197
|
+
model = args[i + 1];
|
|
1198
|
+
i++;
|
|
1199
|
+
continue;
|
|
1200
|
+
}
|
|
1201
|
+
if (arg === '--llm-mode' && i + 1 < args.length) {
|
|
1202
|
+
const value = (args[i + 1] ?? '').trim().toLowerCase();
|
|
1203
|
+
if (value === 'hybrid' || value === 'full') {
|
|
1204
|
+
llmMode = value;
|
|
1205
|
+
}
|
|
1206
|
+
else {
|
|
1207
|
+
console.error(`\n❌ Invalid --llm-mode value: ${args[i + 1]}`);
|
|
1208
|
+
console.log('Allowed values: hybrid, full\n');
|
|
1209
|
+
process.exit(1);
|
|
1210
|
+
}
|
|
1211
|
+
i++;
|
|
1212
|
+
continue;
|
|
1213
|
+
}
|
|
1214
|
+
if (arg === '--allow-flow-fallback') {
|
|
1215
|
+
allowFlowFallback = true;
|
|
1216
|
+
continue;
|
|
1217
|
+
}
|
|
1218
|
+
if (arg === '--direction' && i + 1 < args.length) {
|
|
1219
|
+
const value = (args[i + 1] ?? '').trim().toLowerCase();
|
|
1220
|
+
if (value === 'inbound' || value === 'outbound') {
|
|
1221
|
+
direction = value;
|
|
1222
|
+
}
|
|
1223
|
+
else {
|
|
1224
|
+
console.error(`\n❌ Invalid --direction value: ${args[i + 1]}`);
|
|
1225
|
+
console.log('Allowed values: inbound, outbound\n');
|
|
1226
|
+
process.exit(1);
|
|
1227
|
+
}
|
|
1228
|
+
i++;
|
|
1229
|
+
continue;
|
|
1230
|
+
}
|
|
1231
|
+
if (arg === '--agent-name' && i + 1 < args.length) {
|
|
1232
|
+
agentName = args[i + 1];
|
|
1233
|
+
i++;
|
|
1234
|
+
continue;
|
|
1235
|
+
}
|
|
1236
|
+
if (arg === '--context-file' && i + 1 < args.length) {
|
|
1237
|
+
contextFile = args[i + 1];
|
|
1238
|
+
i++;
|
|
1239
|
+
continue;
|
|
1240
|
+
}
|
|
1241
|
+
if (arg === '--timeout-ms' && i + 1 < args.length) {
|
|
1242
|
+
const parsed = Number(args[i + 1]);
|
|
1243
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
1244
|
+
console.error(`\n❌ Invalid --timeout-ms value: ${args[i + 1]}\n`);
|
|
1245
|
+
process.exit(1);
|
|
1246
|
+
}
|
|
1247
|
+
timeoutMs = Math.floor(parsed);
|
|
1248
|
+
i++;
|
|
1249
|
+
continue;
|
|
1250
|
+
}
|
|
1251
|
+
if (arg === '--interactive') {
|
|
1252
|
+
interactive = true;
|
|
1253
|
+
continue;
|
|
1254
|
+
}
|
|
1255
|
+
if (arg === '--max-questions' && i + 1 < args.length) {
|
|
1256
|
+
const parsed = Number(args[i + 1]);
|
|
1257
|
+
if (!Number.isFinite(parsed) || parsed < 1 || parsed > 6) {
|
|
1258
|
+
console.error(`\n❌ Invalid --max-questions value: ${args[i + 1]}`);
|
|
1259
|
+
console.log('Allowed range: 1..6\n');
|
|
1260
|
+
process.exit(1);
|
|
1261
|
+
}
|
|
1262
|
+
maxQuestions = Math.floor(parsed);
|
|
1263
|
+
i++;
|
|
1264
|
+
continue;
|
|
1265
|
+
}
|
|
1266
|
+
if (arg === '--emit-rfc') {
|
|
1267
|
+
emitRfc = true;
|
|
1268
|
+
continue;
|
|
1269
|
+
}
|
|
1270
|
+
if (arg === '--llm-driven') {
|
|
1271
|
+
llmDriven = true;
|
|
1272
|
+
continue;
|
|
1273
|
+
}
|
|
1274
|
+
if (arg === '--detail-level' && i + 1 < args.length) {
|
|
1275
|
+
const value = (args[i + 1] ?? '').trim().toLowerCase();
|
|
1276
|
+
if (value === 'simple' || value === 'detailed') {
|
|
1277
|
+
detailLevel = value;
|
|
1278
|
+
}
|
|
1279
|
+
else {
|
|
1280
|
+
console.error(`\n❌ Invalid --detail-level value: ${args[i + 1]}`);
|
|
1281
|
+
console.log('Allowed values: simple, detailed\n');
|
|
1282
|
+
process.exit(1);
|
|
1283
|
+
}
|
|
1284
|
+
i++;
|
|
1285
|
+
continue;
|
|
1286
|
+
}
|
|
1287
|
+
if (arg === '--out-dir' && i + 1 < args.length) {
|
|
1288
|
+
outDir = args[i + 1];
|
|
1289
|
+
i++;
|
|
1290
|
+
continue;
|
|
1291
|
+
}
|
|
1292
|
+
if (arg === '--cwd' && i + 1 < args.length) {
|
|
1293
|
+
cwd = resolve(args[i + 1]);
|
|
1294
|
+
i++;
|
|
1295
|
+
continue;
|
|
1296
|
+
}
|
|
1297
|
+
if (arg === '--force') {
|
|
1298
|
+
force = true;
|
|
1299
|
+
continue;
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
if (!useCase || useCase.trim().length === 0) {
|
|
1303
|
+
console.error('\n❌ Missing required --use-case <text>\n');
|
|
1304
|
+
printAgentHelp();
|
|
1305
|
+
process.exit(1);
|
|
1306
|
+
}
|
|
1307
|
+
if (llmDriven) {
|
|
1308
|
+
try {
|
|
1309
|
+
const result = await runArchitect({
|
|
1310
|
+
useCase,
|
|
1311
|
+
mode,
|
|
1312
|
+
target,
|
|
1313
|
+
name,
|
|
1314
|
+
model,
|
|
1315
|
+
contextText: contextFile ? readFileSync(contextFile.startsWith('/') ? contextFile : resolve(cwd, contextFile), 'utf8') : undefined,
|
|
1316
|
+
timeoutMs,
|
|
1317
|
+
interactive,
|
|
1318
|
+
detailLevel,
|
|
1319
|
+
});
|
|
1320
|
+
const artifactRoot = outDir.startsWith('/') ? outDir : resolve(cwd, outDir);
|
|
1321
|
+
const blueprintId = result.blueprint.id;
|
|
1322
|
+
console.log('\n✅ LLM-Driven Agent Architect completed');
|
|
1323
|
+
console.log(` Blueprint ID: ${blueprintId}`);
|
|
1324
|
+
console.log(` Domain: ${result.blueprint.domainAnalysis.domain}`);
|
|
1325
|
+
console.log(` Tools recommended: ${result.blueprint.selectedTools.length}`);
|
|
1326
|
+
const writtenFiles = [];
|
|
1327
|
+
if (result.artifacts.prompt) {
|
|
1328
|
+
const promptPath = join(artifactRoot, 'prompts', `${blueprintId}.system.md`);
|
|
1329
|
+
ensureWritableFile(promptPath, result.artifacts.prompt, force);
|
|
1330
|
+
writtenFiles.push(promptPath);
|
|
1331
|
+
}
|
|
1332
|
+
if (result.artifacts.rfc) {
|
|
1333
|
+
const rfcPath = join(artifactRoot, 'rfcs', `RFC_${blueprintId.toUpperCase().replace(/-/g, '_')}.md`);
|
|
1334
|
+
ensureWritableFile(rfcPath, result.artifacts.rfc, force);
|
|
1335
|
+
writtenFiles.push(rfcPath);
|
|
1336
|
+
}
|
|
1337
|
+
if (result.artifacts.code?.prompt) {
|
|
1338
|
+
const codePath = join(artifactRoot, 'code', 'prompts', `${blueprintId}.prompt.ts`);
|
|
1339
|
+
ensureWritableFile(codePath, result.artifacts.code.prompt, force);
|
|
1340
|
+
writtenFiles.push(codePath);
|
|
1341
|
+
}
|
|
1342
|
+
if (result.artifacts.flow) {
|
|
1343
|
+
const flowPath = join(artifactRoot, 'flow', `${blueprintId}.json`);
|
|
1344
|
+
ensureWritableFile(flowPath, `${JSON.stringify(result.artifacts.flow, null, 2)}\n`, force);
|
|
1345
|
+
writtenFiles.push(flowPath);
|
|
1346
|
+
}
|
|
1347
|
+
if (result.artifacts.code?.flow) {
|
|
1348
|
+
const flowTsPath = join(artifactRoot, 'code', 'flows', `${blueprintId}.flow.ts`);
|
|
1349
|
+
ensureWritableFile(flowTsPath, result.artifacts.code.flow, force);
|
|
1350
|
+
writtenFiles.push(flowTsPath);
|
|
1351
|
+
}
|
|
1352
|
+
const blueprintPath = join(artifactRoot, 'blueprints', `${blueprintId}.blueprint.json`);
|
|
1353
|
+
ensureWritableFile(blueprintPath, `${JSON.stringify(result.blueprint, null, 2)}\n`, force);
|
|
1354
|
+
writtenFiles.push(blueprintPath);
|
|
1355
|
+
console.log('\nArtifacts:');
|
|
1356
|
+
for (const file of writtenFiles) {
|
|
1357
|
+
console.log(` - ${file}`);
|
|
1358
|
+
}
|
|
1359
|
+
console.log('\nSuggested next steps:');
|
|
1360
|
+
console.log(' 1. Review the generated RFC for implementation guidance');
|
|
1361
|
+
console.log(' 2. Implement the recommended tools');
|
|
1362
|
+
console.log(' 3. Test with the evaluation scenarios\n');
|
|
1363
|
+
return;
|
|
1364
|
+
}
|
|
1365
|
+
catch (error) {
|
|
1366
|
+
console.error('\n❌ LLM-Driven Architect failed:', error instanceof Error ? error.message : String(error));
|
|
1367
|
+
process.exit(1);
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
if (interactive && (!process.stdin.isTTY || !process.stdout.isTTY)) {
|
|
1371
|
+
console.log('\n⚠️ Interactive mode requested but no TTY detected. Continuing in non-interactive mode.');
|
|
1372
|
+
interactive = false;
|
|
1373
|
+
}
|
|
1374
|
+
const clarifications = interactive ? await collectClarifications(maxQuestions) : [];
|
|
1375
|
+
const clarificationToolLine = clarifications
|
|
1376
|
+
.find(item => /required tools|capabilities/i.test(item.question))
|
|
1377
|
+
?.answer;
|
|
1378
|
+
if (clarificationToolLine) {
|
|
1379
|
+
tools = uniqueStrings([...tools, ...parseCsv(clarificationToolLine)]);
|
|
1380
|
+
}
|
|
1381
|
+
if (tools.length === 0) {
|
|
1382
|
+
tools = await discoverToolCandidates(cwd);
|
|
1383
|
+
}
|
|
1384
|
+
const suggestedToolSlots = inferSuggestedToolSlots(useCase);
|
|
1385
|
+
const contextParts = [];
|
|
1386
|
+
if (contextFile) {
|
|
1387
|
+
const resolvedContextPath = contextFile.startsWith('/') ? contextFile : resolve(cwd, contextFile);
|
|
1388
|
+
contextParts.push(readFileSync(resolvedContextPath, 'utf8'));
|
|
1389
|
+
}
|
|
1390
|
+
if (clarifications.length > 0) {
|
|
1391
|
+
contextParts.push(`Interactive clarifications:\n${clarifications.map(item => `- ${item.question} ${item.answer}`).join('\n')}`);
|
|
1392
|
+
}
|
|
1393
|
+
const contextText = contextParts.length > 0 ? contextParts.join('\n\n') : undefined;
|
|
1394
|
+
let flow;
|
|
1395
|
+
let flowFallbackUsed = false;
|
|
1396
|
+
let flowFallbackReason;
|
|
1397
|
+
if (mode !== 'llm') {
|
|
1398
|
+
let generatedFlow;
|
|
1399
|
+
try {
|
|
1400
|
+
generatedFlow = await generateFlowTemplateWithLLM({
|
|
1401
|
+
useCase,
|
|
1402
|
+
direction,
|
|
1403
|
+
agentName,
|
|
1404
|
+
model,
|
|
1405
|
+
llmMode,
|
|
1406
|
+
contextText,
|
|
1407
|
+
timeoutMs,
|
|
1408
|
+
});
|
|
1409
|
+
}
|
|
1410
|
+
catch (error) {
|
|
1411
|
+
if (!allowFlowFallback) {
|
|
1412
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
1413
|
+
throw new Error(`LLM flow generation failed and fallback is disabled. Reason: ${reason}\n` +
|
|
1414
|
+
'Re-run with --allow-flow-fallback to scaffold deterministic flow structure only.');
|
|
1415
|
+
}
|
|
1416
|
+
flowFallbackUsed = true;
|
|
1417
|
+
flowFallbackReason = error instanceof Error ? error.message : String(error);
|
|
1418
|
+
console.log('\n⚠️ LLM flow generation failed. Applying deterministic flow structure fallback (--allow-flow-fallback).');
|
|
1419
|
+
generatedFlow = generateFlowTemplate({
|
|
1420
|
+
useCase,
|
|
1421
|
+
direction,
|
|
1422
|
+
agentName,
|
|
1423
|
+
});
|
|
1424
|
+
}
|
|
1425
|
+
flow = normalizeFlowDraft(addTransitionGuidance(addToolGuidanceToFlow(generatedFlow, tools, suggestedToolSlots)));
|
|
1426
|
+
}
|
|
1427
|
+
const blueprintId = toSlug(name ?? useCase);
|
|
1428
|
+
const displayName = name ? toTitleCase(name) : toTitleCase(blueprintId);
|
|
1429
|
+
const promptLayers = buildPromptLayers(useCase, mode, tools, suggestedToolSlots, clarifications);
|
|
1430
|
+
const promptMarkdown = flow
|
|
1431
|
+
? buildFlowBackedPromptMarkdown(displayName, useCase, promptLayers, flow, tools)
|
|
1432
|
+
: buildLayeredPromptMarkdown(displayName, promptLayers);
|
|
1433
|
+
const promptConstName = toTsConstCase(blueprintId);
|
|
1434
|
+
const blueprint = {
|
|
1435
|
+
id: blueprintId,
|
|
1436
|
+
name: displayName,
|
|
1437
|
+
useCase,
|
|
1438
|
+
mode,
|
|
1439
|
+
target,
|
|
1440
|
+
selectedTools: tools,
|
|
1441
|
+
suggestedToolSlots,
|
|
1442
|
+
promptLayers,
|
|
1443
|
+
flowPlan: flow
|
|
1444
|
+
? {
|
|
1445
|
+
id: flow.id ?? `${blueprintId}-flow`,
|
|
1446
|
+
entry: flow.entry ?? 'start',
|
|
1447
|
+
nodeIds: flow.nodes.map(node => node.id),
|
|
1448
|
+
transitionEvents: (flow.transitions ?? [])
|
|
1449
|
+
.map(transition => transition.on)
|
|
1450
|
+
.filter((value) => typeof value === 'string' && value.length > 0),
|
|
1451
|
+
}
|
|
1452
|
+
: undefined,
|
|
1453
|
+
evalPlan: {
|
|
1454
|
+
scenarios: [
|
|
1455
|
+
'happy_path',
|
|
1456
|
+
'ambiguous_user_intent',
|
|
1457
|
+
'tool_failure_path',
|
|
1458
|
+
'policy_refusal_or_escalation',
|
|
1459
|
+
],
|
|
1460
|
+
},
|
|
1461
|
+
generationMeta: {
|
|
1462
|
+
generatedAt: new Date().toISOString(),
|
|
1463
|
+
model,
|
|
1464
|
+
llmRequired: true,
|
|
1465
|
+
llmEnabled: true,
|
|
1466
|
+
llmMode,
|
|
1467
|
+
fallbackUsed: flowFallbackUsed,
|
|
1468
|
+
fallbackType: flowFallbackUsed ? 'flow-structure-only' : undefined,
|
|
1469
|
+
fallbackReason: flowFallbackReason,
|
|
1470
|
+
interactive,
|
|
1471
|
+
clarificationCount: clarifications.length,
|
|
1472
|
+
clarifications,
|
|
1473
|
+
},
|
|
1474
|
+
};
|
|
1475
|
+
const artifactRoot = outDir.startsWith('/') ? outDir : resolve(cwd, outDir);
|
|
1476
|
+
const writtenFiles = [];
|
|
1477
|
+
const promptFilesForLint = [];
|
|
1478
|
+
const blueprintPath = join(artifactRoot, 'blueprints', `${blueprintId}.blueprint.json`);
|
|
1479
|
+
ensureWritableFile(blueprintPath, `${JSON.stringify(blueprint, null, 2)}\n`, force);
|
|
1480
|
+
writtenFiles.push(blueprintPath);
|
|
1481
|
+
if (target === 'config' || target === 'both') {
|
|
1482
|
+
const promptPath = join(artifactRoot, 'prompts', `${blueprintId}.system.md`);
|
|
1483
|
+
ensureWritableFile(promptPath, promptMarkdown, force);
|
|
1484
|
+
writtenFiles.push(promptPath);
|
|
1485
|
+
promptFilesForLint.push(promptPath);
|
|
1486
|
+
if (flow) {
|
|
1487
|
+
const flowPath = join(artifactRoot, 'flow', `${blueprintId}.json`);
|
|
1488
|
+
ensureWritableFile(flowPath, `${JSON.stringify(flow, null, 2)}\n`, force);
|
|
1489
|
+
writtenFiles.push(flowPath);
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
if (target === 'code' || target === 'both') {
|
|
1493
|
+
const promptTsPath = join(artifactRoot, 'code', 'prompts', `${blueprintId}.prompt.ts`);
|
|
1494
|
+
ensureWritableFile(promptTsPath, buildPromptTypeScriptModule(promptConstName, promptMarkdown), force);
|
|
1495
|
+
writtenFiles.push(promptTsPath);
|
|
1496
|
+
if (flow) {
|
|
1497
|
+
const flowTsPath = join(artifactRoot, 'code', 'flows', `${blueprintId}.flow.ts`);
|
|
1498
|
+
const flowTs = emitFlowTypeScriptModule(flow, { constName: `${promptConstName}Flow` });
|
|
1499
|
+
ensureWritableFile(flowTsPath, flowTs, force);
|
|
1500
|
+
writtenFiles.push(flowTsPath);
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
if (emitRfc) {
|
|
1504
|
+
const rfcPath = join(artifactRoot, 'rfcs', `RFC_${blueprintId.toUpperCase().replace(/-/g, '_')}.md`);
|
|
1505
|
+
const rfcMarkdown = buildBlueprintRfcMarkdown(displayName, blueprint, writtenFiles);
|
|
1506
|
+
ensureWritableFile(rfcPath, rfcMarkdown, force);
|
|
1507
|
+
writtenFiles.push(rfcPath);
|
|
1508
|
+
}
|
|
1509
|
+
if (promptFilesForLint.length > 0) {
|
|
1510
|
+
const issues = [];
|
|
1511
|
+
const triageRuleIds = new Set([
|
|
1512
|
+
'triage-missing-routing-directive',
|
|
1513
|
+
'triage-user-leak-risk',
|
|
1514
|
+
'triage-not-structured',
|
|
1515
|
+
'sop-in-prompt',
|
|
1516
|
+
'prompt-too-long',
|
|
1517
|
+
]);
|
|
1518
|
+
for (const promptPath of promptFilesForLint) {
|
|
1519
|
+
const lintResult = lintPrompts({ cwd: artifactRoot, path: promptPath });
|
|
1520
|
+
for (const issue of lintResult.issues) {
|
|
1521
|
+
if (triageRuleIds.has(issue.ruleId)) {
|
|
1522
|
+
continue;
|
|
1523
|
+
}
|
|
1524
|
+
issues.push(issue);
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
if (issues.length > 0) {
|
|
1528
|
+
console.log('\n❌ Generated prompts failed lint gate:\n');
|
|
1529
|
+
for (const issue of issues) {
|
|
1530
|
+
const relFile = issue.file.startsWith(artifactRoot)
|
|
1531
|
+
? `.${issue.file.slice(artifactRoot.length)}`
|
|
1532
|
+
: issue.file;
|
|
1533
|
+
console.log(`- [${issue.severity}] ${issue.ruleId}: ${issue.message}`);
|
|
1534
|
+
console.log(` File: ${relFile}`);
|
|
1535
|
+
}
|
|
1536
|
+
process.exit(1);
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
console.log('\n✅ Agent blueprint generated:');
|
|
1540
|
+
console.log(` ID: ${blueprintId}`);
|
|
1541
|
+
console.log(` Mode: ${mode}`);
|
|
1542
|
+
console.log(` Target: ${target}`);
|
|
1543
|
+
console.log(` LLM generation: required (${model}, ${llmMode})`);
|
|
1544
|
+
if (flowFallbackUsed) {
|
|
1545
|
+
console.log(' Flow fallback: deterministic structure-only');
|
|
1546
|
+
}
|
|
1547
|
+
if (interactive) {
|
|
1548
|
+
console.log(` Clarification loop: ${clarifications.length} answer(s) captured (max ${maxQuestions})`);
|
|
1549
|
+
}
|
|
1550
|
+
console.log('\nArtifacts:');
|
|
1551
|
+
for (const file of writtenFiles) {
|
|
1552
|
+
console.log(` - ${file}`);
|
|
1553
|
+
}
|
|
1554
|
+
console.log('\nSuggested next steps:');
|
|
1555
|
+
console.log(' 1. Review generated blueprint and prompt layers');
|
|
1556
|
+
console.log(' 2. Run domain transcripts against generated artifacts');
|
|
1557
|
+
console.log(' 3. Add or tune tool contracts before production rollout\n');
|
|
1558
|
+
}
|
|
1559
|
+
async function cmdFlowGenerate(args) {
|
|
1560
|
+
let useCase;
|
|
1561
|
+
let flowId;
|
|
1562
|
+
let direction = 'inbound';
|
|
1563
|
+
let agentName;
|
|
1564
|
+
let model = 'gpt-5-mini';
|
|
1565
|
+
let llmMode = 'hybrid';
|
|
1566
|
+
let timeoutMs;
|
|
1567
|
+
let useLlm = false;
|
|
1568
|
+
let contextFile;
|
|
1569
|
+
let outPath;
|
|
1570
|
+
let outTsPath;
|
|
1571
|
+
let tsConstName;
|
|
1572
|
+
let cwd = process.cwd();
|
|
1573
|
+
let force = false;
|
|
1574
|
+
for (let i = 0; i < args.length; i++) {
|
|
1575
|
+
const arg = args[i];
|
|
1576
|
+
if (arg === '--use-case' && i + 1 < args.length) {
|
|
1577
|
+
useCase = args[i + 1];
|
|
1578
|
+
i++;
|
|
1579
|
+
continue;
|
|
1580
|
+
}
|
|
1581
|
+
if (arg === '--id' && i + 1 < args.length) {
|
|
1582
|
+
flowId = args[i + 1];
|
|
1583
|
+
i++;
|
|
1584
|
+
continue;
|
|
1585
|
+
}
|
|
1586
|
+
if (arg === '--direction' && i + 1 < args.length) {
|
|
1587
|
+
const value = (args[i + 1] ?? '').trim().toLowerCase();
|
|
1588
|
+
if (value === 'inbound' || value === 'outbound') {
|
|
1589
|
+
direction = value;
|
|
1590
|
+
}
|
|
1591
|
+
else {
|
|
1592
|
+
console.error(`\n❌ Invalid --direction value: ${args[i + 1]}`);
|
|
1593
|
+
console.log('Allowed values: inbound, outbound\n');
|
|
1594
|
+
process.exit(1);
|
|
1595
|
+
}
|
|
1596
|
+
i++;
|
|
1597
|
+
continue;
|
|
1598
|
+
}
|
|
1599
|
+
if (arg === '--agent-name' && i + 1 < args.length) {
|
|
1600
|
+
agentName = args[i + 1];
|
|
1601
|
+
i++;
|
|
1602
|
+
continue;
|
|
1603
|
+
}
|
|
1604
|
+
if (arg === '--model' && i + 1 < args.length) {
|
|
1605
|
+
model = args[i + 1];
|
|
1606
|
+
i++;
|
|
1607
|
+
continue;
|
|
1608
|
+
}
|
|
1609
|
+
if (arg === '--llm-mode' && i + 1 < args.length) {
|
|
1610
|
+
const mode = (args[i + 1] ?? '').trim().toLowerCase();
|
|
1611
|
+
if (mode === 'hybrid' || mode === 'full') {
|
|
1612
|
+
llmMode = mode;
|
|
1613
|
+
}
|
|
1614
|
+
else {
|
|
1615
|
+
console.error(`\n❌ Invalid --llm-mode value: ${args[i + 1]}`);
|
|
1616
|
+
console.log('Allowed values: hybrid, full\n');
|
|
1617
|
+
process.exit(1);
|
|
1618
|
+
}
|
|
1619
|
+
i++;
|
|
1620
|
+
continue;
|
|
1621
|
+
}
|
|
1622
|
+
if (arg === '--context-file' && i + 1 < args.length) {
|
|
1623
|
+
contextFile = args[i + 1];
|
|
1624
|
+
i++;
|
|
1625
|
+
continue;
|
|
1626
|
+
}
|
|
1627
|
+
if (arg === '--timeout-ms' && i + 1 < args.length) {
|
|
1628
|
+
const parsed = Number(args[i + 1]);
|
|
1629
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
1630
|
+
console.error(`\n❌ Invalid --timeout-ms value: ${args[i + 1]}\n`);
|
|
1631
|
+
process.exit(1);
|
|
1632
|
+
}
|
|
1633
|
+
timeoutMs = Math.floor(parsed);
|
|
1634
|
+
i++;
|
|
1635
|
+
continue;
|
|
1636
|
+
}
|
|
1637
|
+
if (arg === '--out' && i + 1 < args.length) {
|
|
1638
|
+
outPath = args[i + 1];
|
|
1639
|
+
i++;
|
|
1640
|
+
continue;
|
|
1641
|
+
}
|
|
1642
|
+
if (arg === '--out-ts' && i + 1 < args.length) {
|
|
1643
|
+
outTsPath = args[i + 1];
|
|
1644
|
+
i++;
|
|
1645
|
+
continue;
|
|
1646
|
+
}
|
|
1647
|
+
if (arg === '--ts-const' && i + 1 < args.length) {
|
|
1648
|
+
tsConstName = args[i + 1];
|
|
1649
|
+
i++;
|
|
1650
|
+
continue;
|
|
1651
|
+
}
|
|
1652
|
+
if (arg === '--cwd' && i + 1 < args.length) {
|
|
1653
|
+
cwd = resolve(args[i + 1]);
|
|
1654
|
+
i++;
|
|
1655
|
+
continue;
|
|
1656
|
+
}
|
|
1657
|
+
if (arg === '--force') {
|
|
1658
|
+
force = true;
|
|
1659
|
+
}
|
|
1660
|
+
if (arg === '--llm') {
|
|
1661
|
+
useLlm = true;
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
if (!useCase || useCase.trim().length === 0) {
|
|
1665
|
+
console.error('\n❌ Missing required --use-case <text>\n');
|
|
1666
|
+
printFlowHelp();
|
|
1667
|
+
process.exit(1);
|
|
1668
|
+
}
|
|
1669
|
+
const contextText = contextFile
|
|
1670
|
+
? readFileSync(contextFile.startsWith('/') ? contextFile : resolve(cwd, contextFile), 'utf8')
|
|
1671
|
+
: undefined;
|
|
1672
|
+
const flow = useLlm
|
|
1673
|
+
? await generateFlowTemplateWithLLM({
|
|
1674
|
+
useCase,
|
|
1675
|
+
flowId,
|
|
1676
|
+
direction,
|
|
1677
|
+
agentName,
|
|
1678
|
+
model,
|
|
1679
|
+
llmMode,
|
|
1680
|
+
contextText,
|
|
1681
|
+
timeoutMs,
|
|
1682
|
+
})
|
|
1683
|
+
: generateFlowTemplate({
|
|
1684
|
+
useCase,
|
|
1685
|
+
flowId,
|
|
1686
|
+
direction,
|
|
1687
|
+
agentName,
|
|
1688
|
+
});
|
|
1689
|
+
const output = `${JSON.stringify(flow, null, 2)}\n`;
|
|
1690
|
+
const tsOutput = emitFlowTypeScriptModule(flow, { constName: tsConstName });
|
|
1691
|
+
if (!outPath && !outTsPath) {
|
|
1692
|
+
process.stdout.write(output);
|
|
1693
|
+
return;
|
|
1694
|
+
}
|
|
1695
|
+
const writtenFiles = [];
|
|
1696
|
+
if (outPath) {
|
|
1697
|
+
const target = outPath.startsWith('/') ? outPath : resolve(cwd, outPath);
|
|
1698
|
+
if (existsSync(target) && !force) {
|
|
1699
|
+
console.error(`\n❌ File already exists: ${target}`);
|
|
1700
|
+
console.log('Use --force to overwrite.\n');
|
|
1701
|
+
process.exit(1);
|
|
1702
|
+
}
|
|
1703
|
+
const parentDir = dirname(target);
|
|
1704
|
+
if (!existsSync(parentDir)) {
|
|
1705
|
+
mkdirSync(parentDir, { recursive: true });
|
|
1706
|
+
}
|
|
1707
|
+
writeFileSync(target, output, 'utf8');
|
|
1708
|
+
writtenFiles.push(target);
|
|
1709
|
+
}
|
|
1710
|
+
if (outTsPath) {
|
|
1711
|
+
const targetTs = outTsPath.startsWith('/') ? outTsPath : resolve(cwd, outTsPath);
|
|
1712
|
+
if (existsSync(targetTs) && !force) {
|
|
1713
|
+
console.error(`\n❌ File already exists: ${targetTs}`);
|
|
1714
|
+
console.log('Use --force to overwrite.\n');
|
|
1715
|
+
process.exit(1);
|
|
1716
|
+
}
|
|
1717
|
+
const parentDirTs = dirname(targetTs);
|
|
1718
|
+
if (!existsSync(parentDirTs)) {
|
|
1719
|
+
mkdirSync(parentDirTs, { recursive: true });
|
|
1720
|
+
}
|
|
1721
|
+
writeFileSync(targetTs, tsOutput, 'utf8');
|
|
1722
|
+
writtenFiles.push(targetTs);
|
|
1723
|
+
}
|
|
1724
|
+
console.log('\n✅ Flow template generated:');
|
|
1725
|
+
for (const file of writtenFiles) {
|
|
1726
|
+
console.log(` - ${file}`);
|
|
1727
|
+
}
|
|
1728
|
+
if (useLlm) {
|
|
1729
|
+
console.log(` Model: ${model}`);
|
|
1730
|
+
console.log(` LLM mode: ${llmMode}`);
|
|
1731
|
+
}
|
|
1732
|
+
console.log('\nSuggested next steps:');
|
|
1733
|
+
console.log(' 1. Replace Task sections with domain-specific questions and data rules');
|
|
1734
|
+
console.log(' 2. (Optional) Add explicit transition tools if you need custom transition payload logic');
|
|
1735
|
+
console.log(' 3. Run: ariaflow validate\n');
|
|
1736
|
+
}
|
|
1737
|
+
function printFlowHelp() {
|
|
1738
|
+
console.log(`
|
|
1739
|
+
AriaFlow Flow Generator
|
|
1740
|
+
|
|
1741
|
+
Usage:
|
|
1742
|
+
ariaflow flow generate --use-case <text> [--id <flow-id>] [--direction inbound|outbound] [--agent-name <name>] [--llm --model <id> --llm-mode hybrid|full] [--context-file <path>] [--timeout-ms <n>] [--out <path>] [--out-ts <path>] [--ts-const <name>] [--cwd <dir>] [--force]
|
|
1743
|
+
|
|
1744
|
+
Generates a production-style flow JSON scaffold with:
|
|
1745
|
+
- global role prompt
|
|
1746
|
+
- start/main/summary/end node prompts
|
|
1747
|
+
- transition contract metadata (label, conditionText, toolOnly, requiresUserTurn)
|
|
1748
|
+
- optional LLM-authored prompt blocks (--llm)
|
|
1749
|
+
- default LLM mode is hybrid: static scaffold + variable domain blocks
|
|
1750
|
+
|
|
1751
|
+
Options:
|
|
1752
|
+
--use-case <text> Use-case description (required)
|
|
1753
|
+
--id <flow-id> Flow id override (optional)
|
|
1754
|
+
--direction <value> inbound | outbound (default: inbound)
|
|
1755
|
+
--agent-name <name> Agent name used in global prompt (default: Sam)
|
|
1756
|
+
--llm Use an LLM model to generate global/main-agenda prompts
|
|
1757
|
+
--model <id> OpenAI model id for --llm (default: gpt-5-mini; e.g. gpt-5-mini, gpt-4.1, openai:gpt-4.1)
|
|
1758
|
+
--llm-mode <mode> hybrid | full (default: hybrid)
|
|
1759
|
+
--context-file <path> Additional input context (YAML/MD/TXT) for --llm generation
|
|
1760
|
+
--timeout-ms <n> LLM generation timeout in ms (default: 45000)
|
|
1761
|
+
--out <path> Write JSON flow output to file
|
|
1762
|
+
--out-ts <path> Write typed TypeScript flow module for code-first agents
|
|
1763
|
+
--ts-const <name> Constant name for --out-ts module (default: generatedFlow)
|
|
1764
|
+
--cwd <dir> Base directory for relative --out paths
|
|
1765
|
+
--force Overwrite existing output file
|
|
1766
|
+
|
|
1767
|
+
Examples:
|
|
1768
|
+
ariaflow flow generate --use-case "hr technical screening"
|
|
1769
|
+
ariaflow flow generate --use-case "medical appointment scheduling" --out ./.ariaflow/flow/appointments.json --out-ts ./src/flows/appointments-flow.ts
|
|
1770
|
+
ariaflow flow generate --use-case "sales with leads extraction and research" --llm --model gpt-5-mini --llm-mode hybrid --out ./.ariaflow/flow/sales.json
|
|
1771
|
+
ariaflow flow generate --use-case "form filling from yaml" --llm --model gpt-5-mini --context-file ./.ariaflow/data/schedule_form.yaml
|
|
549
1772
|
`);
|
|
550
1773
|
}
|
|
551
1774
|
main().catch((error) => {
|