@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.
- 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 +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +890 -0
- package/dist/cli.js.map +1 -1
- package/dist/llm-flow-generator.d.ts.map +1 -1
- package/dist/llm-flow-generator.js +31 -73
- package/dist/llm-flow-generator.js.map +1 -1
- package/dist/loader.js +13 -13
- package/dist/loader.js.map +1 -1
- package/dist/prompt-lint.js +4 -4
- package/dist/prompt-lint.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/guide/README.md +95 -0
- 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;
|