@ariaflowagents/config 0.7.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/agent-architect/index.d.ts +10 -0
  2. package/dist/agent-architect/index.d.ts.map +1 -0
  3. package/dist/agent-architect/index.js +131 -0
  4. package/dist/agent-architect/index.js.map +1 -0
  5. package/dist/agent-architect/prompt-generator.d.ts +9 -0
  6. package/dist/agent-architect/prompt-generator.d.ts.map +1 -0
  7. package/dist/agent-architect/prompt-generator.js +155 -0
  8. package/dist/agent-architect/prompt-generator.js.map +1 -0
  9. package/dist/agent-architect/rfc-emitter.d.ts +18 -0
  10. package/dist/agent-architect/rfc-emitter.d.ts.map +1 -0
  11. package/dist/agent-architect/rfc-emitter.js +350 -0
  12. package/dist/agent-architect/rfc-emitter.js.map +1 -0
  13. package/dist/agent-architect/tool-recommender.d.ts +8 -0
  14. package/dist/agent-architect/tool-recommender.d.ts.map +1 -0
  15. package/dist/agent-architect/tool-recommender.js +87 -0
  16. package/dist/agent-architect/tool-recommender.js.map +1 -0
  17. package/dist/agent-architect/types.d.ts +904 -0
  18. package/dist/agent-architect/types.d.ts.map +1 -0
  19. package/dist/agent-architect/types.js +89 -0
  20. package/dist/agent-architect/types.js.map +1 -0
  21. package/dist/agent-architect/use-case-analyzer.d.ts +8 -0
  22. package/dist/agent-architect/use-case-analyzer.d.ts.map +1 -0
  23. package/dist/agent-architect/use-case-analyzer.js +76 -0
  24. package/dist/agent-architect/use-case-analyzer.js.map +1 -0
  25. package/dist/cli.d.ts +1 -0
  26. package/dist/cli.d.ts.map +1 -1
  27. package/dist/cli.js +890 -0
  28. package/dist/cli.js.map +1 -1
  29. package/dist/llm-flow-generator.d.ts.map +1 -1
  30. package/dist/llm-flow-generator.js +31 -73
  31. package/dist/llm-flow-generator.js.map +1 -1
  32. package/dist/loader.js +13 -13
  33. package/dist/loader.js.map +1 -1
  34. package/dist/prompt-lint.js +4 -4
  35. package/dist/prompt-lint.js.map +1 -1
  36. package/dist/types.d.ts +1 -1
  37. package/dist/types.d.ts.map +1 -1
  38. package/guide/README.md +95 -0
  39. package/package.json +10 -10
package/dist/cli.js CHANGED
@@ -11,15 +11,19 @@
11
11
  * npx ariaflow prompt lint [--path <file-or-dir>] [--config <path>] [--strict] [--json]
12
12
  * npx ariaflow pack init <name> [--starter <id>] [--cwd <dir>] [--force]
13
13
  * npx ariaflow flow generate --use-case <text> [--out <path>]
14
+ * npx ariaflow agent architect --use-case <text> [--mode llm|flow|hybrid]
14
15
  */
15
16
  import { join, dirname, resolve, relative } from 'path';
16
17
  import { fileURLToPath } from 'url';
17
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';
18
21
  import { loadAriaflowConfigWithResult, printLoadSummary } from './loader.js';
19
22
  import { generateFlowTemplate } from './flow-generator.js';
20
23
  import { generateFlowTemplateWithLLM } from './llm-flow-generator.js';
21
24
  import { emitFlowTypeScriptModule } from './flow-codegen.js';
22
25
  import { lintPrompts } from './prompt-lint.js';
26
+ import { runArchitect } from './agent-architect/index.js';
23
27
  const __dirname = dirname(fileURLToPath(import.meta.url));
24
28
  const AVAILABLE_STARTERS = ['basic', 'sales', 'support', 'bank-hybrid'];
25
29
  const AVAILABLE_PACK_STARTERS = ['basic'];
@@ -80,6 +84,16 @@ async function main() {
80
84
  printFlowHelp();
81
85
  process.exit(0);
82
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
+ }
83
97
  const options = {
84
98
  config: process.env.ARIAFLOW_CONFIG,
85
99
  };
@@ -358,6 +372,7 @@ Commands:
358
372
  prompt init Create a markdown prompt template file
359
373
  prompt lint Lint prompt files with Aria quality gates
360
374
  flow generate Generate a production-style flow JSON scaffold
375
+ agent architect Generate blueprint + prompt/flow/code artifacts
361
376
  help Show this help
362
377
 
363
378
  Options:
@@ -375,6 +390,7 @@ Examples:
375
390
  ariaflow prompt init --template support-agent
376
391
  ariaflow prompt lint --strict
377
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
378
394
  `);
379
395
  }
380
396
  async function cmdPackInit(args) {
@@ -666,6 +682,880 @@ async function cmdPromptLint(args) {
666
682
  }
667
683
  console.log('✅ Prompt lint passed with warnings.\n');
668
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
+ }
669
1559
  async function cmdFlowGenerate(args) {
670
1560
  let useCase;
671
1561
  let flowId;