agentxchain 2.25.1 → 2.26.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 +1 -0
- package/bin/agentxchain.js +2 -2
- package/package.json +1 -1
- package/src/commands/init.js +259 -43
- package/src/commands/template-list.js +4 -0
- package/src/commands/template-set.js +6 -0
- package/src/lib/gate-evaluator.js +142 -30
- package/src/lib/governed-state.js +3 -1
- package/src/lib/governed-templates.js +152 -25
- package/src/lib/normalized-config.js +191 -1
- package/src/lib/workflow-gate-semantics.js +59 -0
- package/src/templates/governed/enterprise-app.json +195 -0
package/README.md
CHANGED
|
@@ -88,6 +88,7 @@ Built-in governed templates:
|
|
|
88
88
|
- `cli-tool`: command surface, platform support, distribution checklist
|
|
89
89
|
- `library`: public API, compatibility policy, release and adoption checklist
|
|
90
90
|
- `web-app`: user flows, UI acceptance, browser support
|
|
91
|
+
- `enterprise-app`: enterprise planning artifacts plus blueprint-backed `architect` and `security_reviewer` phases
|
|
91
92
|
|
|
92
93
|
`step` writes a turn-scoped bundle under `.agentxchain/dispatch/turns/<turn_id>/` and expects a staged result at `.agentxchain/staging/<turn_id>/turn-result.json`. Typical continuation:
|
|
93
94
|
|
package/bin/agentxchain.js
CHANGED
|
@@ -120,7 +120,7 @@ program
|
|
|
120
120
|
.option('-y, --yes', 'Skip prompts, use defaults')
|
|
121
121
|
.option('--governed', 'Create a governed project (orchestrator-owned state)')
|
|
122
122
|
.option('--dir <path>', 'Scaffold target directory. Use "." for in-place bootstrap.')
|
|
123
|
-
.option('--template <id>', 'Governed scaffold template: generic, api-service, cli-tool, library, web-app')
|
|
123
|
+
.option('--template <id>', 'Governed scaffold template: generic, api-service, cli-tool, library, web-app, enterprise-app')
|
|
124
124
|
.option('--dev-command <parts...>', 'Governed local-dev command parts. Include {prompt} for argv prompt delivery.')
|
|
125
125
|
.option('--dev-prompt-transport <mode>', 'Governed local-dev prompt transport: argv, stdin, dispatch_bundle_only')
|
|
126
126
|
.option('--schema-version <version>', 'Schema version (3 for legacy, or use --governed for current)')
|
|
@@ -469,7 +469,7 @@ intakeCmd
|
|
|
469
469
|
.description('Triage a detected intent — set priority, template, charter, and acceptance')
|
|
470
470
|
.option('--intent <id>', 'Intent ID to triage')
|
|
471
471
|
.option('--priority <level>', 'Priority level (p0, p1, p2, p3)')
|
|
472
|
-
.option('--template <id>', 'Governed template (generic, api-service, cli-tool, library, web-app)')
|
|
472
|
+
.option('--template <id>', 'Governed template (generic, api-service, cli-tool, library, web-app, enterprise-app)')
|
|
473
473
|
.option('--charter <text>', 'Delivery charter text')
|
|
474
474
|
.option('--acceptance <text>', 'Comma-separated acceptance criteria')
|
|
475
475
|
.option('--suppress', 'Suppress the intent instead of triaging')
|
package/package.json
CHANGED
package/src/commands/init.js
CHANGED
|
@@ -142,7 +142,7 @@ const GOVERNED_GATES = {
|
|
|
142
142
|
}
|
|
143
143
|
};
|
|
144
144
|
|
|
145
|
-
function buildGovernedPrompt(roleId, role) {
|
|
145
|
+
function buildGovernedPrompt(roleId, role, scaffoldContext = {}) {
|
|
146
146
|
const rolePrompts = {
|
|
147
147
|
pm: buildPmPrompt,
|
|
148
148
|
dev: buildDevPrompt,
|
|
@@ -154,7 +154,7 @@ function buildGovernedPrompt(roleId, role) {
|
|
|
154
154
|
if (builder) return builder(role);
|
|
155
155
|
|
|
156
156
|
// Fallback for custom roles
|
|
157
|
-
return buildGenericPrompt(roleId, role);
|
|
157
|
+
return buildGenericPrompt(roleId, role, scaffoldContext);
|
|
158
158
|
}
|
|
159
159
|
|
|
160
160
|
function buildPmPrompt(role) {
|
|
@@ -351,7 +351,66 @@ If you cannot resolve the deadlock:
|
|
|
351
351
|
`;
|
|
352
352
|
}
|
|
353
353
|
|
|
354
|
-
function
|
|
354
|
+
function summarizeRoleWorkflow(roleId, scaffoldContext = {}) {
|
|
355
|
+
const routing = scaffoldContext.routing || {};
|
|
356
|
+
const gates = scaffoldContext.gates || {};
|
|
357
|
+
const workflowKitConfig = scaffoldContext.workflowKitConfig || {};
|
|
358
|
+
const ownedPhases = Object.entries(routing)
|
|
359
|
+
.filter(([, route]) => route?.entry_role === roleId)
|
|
360
|
+
.map(([phaseName]) => phaseName);
|
|
361
|
+
const gateLines = [];
|
|
362
|
+
const artifactLines = [];
|
|
363
|
+
const ownershipLines = [];
|
|
364
|
+
|
|
365
|
+
for (const phaseName of ownedPhases) {
|
|
366
|
+
const exitGate = routing[phaseName]?.exit_gate;
|
|
367
|
+
if (exitGate) {
|
|
368
|
+
gateLines.push(`- ${phaseName}: ${exitGate}`);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const phaseArtifacts = Array.isArray(workflowKitConfig?.phases?.[phaseName]?.artifacts)
|
|
372
|
+
? workflowKitConfig.phases[phaseName].artifacts.filter((artifact) => artifact?.path)
|
|
373
|
+
: [];
|
|
374
|
+
const workflowArtifacts = phaseArtifacts
|
|
375
|
+
.map((artifact) => artifact.path)
|
|
376
|
+
.filter(Boolean);
|
|
377
|
+
const gateArtifacts = Array.isArray(gates?.[exitGate]?.requires_files)
|
|
378
|
+
? gates[exitGate].requires_files.filter(Boolean)
|
|
379
|
+
: [];
|
|
380
|
+
const ownedArtifacts = [...new Set([...workflowArtifacts, ...gateArtifacts])];
|
|
381
|
+
if (ownedArtifacts.length > 0) {
|
|
382
|
+
artifactLines.push(`- ${phaseName}: ${ownedArtifacts.join(', ')}`);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const enforcedArtifacts = phaseArtifacts
|
|
386
|
+
.filter((artifact) => artifact.owned_by === roleId)
|
|
387
|
+
.map((artifact) => artifact.path);
|
|
388
|
+
if (enforcedArtifacts.length > 0) {
|
|
389
|
+
const verb = enforcedArtifacts.length === 1 ? 'requires' : 'require';
|
|
390
|
+
ownershipLines.push(
|
|
391
|
+
`- ${phaseName}: ${enforcedArtifacts.join(', ')} ${verb} an accepted turn from you before the gate can pass`,
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
return { ownedPhases, gateLines, artifactLines, ownershipLines };
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function buildGenericPrompt(roleId, role, scaffoldContext = {}) {
|
|
400
|
+
const workflowSummary = summarizeRoleWorkflow(roleId, scaffoldContext);
|
|
401
|
+
const primaryPhasesSection = workflowSummary.ownedPhases.length > 0
|
|
402
|
+
? `\n## Primary Phases\n\n- ${workflowSummary.ownedPhases.join(', ')}\n`
|
|
403
|
+
: '';
|
|
404
|
+
const phaseGatesSection = workflowSummary.gateLines.length > 0
|
|
405
|
+
? `\n## Phase Gates\n\n${workflowSummary.gateLines.join('\n')}\n`
|
|
406
|
+
: '';
|
|
407
|
+
const workflowArtifactsSection = workflowSummary.artifactLines.length > 0
|
|
408
|
+
? `\n## Workflow Artifacts You Own\n\n${workflowSummary.artifactLines.join('\n')}\n`
|
|
409
|
+
: '';
|
|
410
|
+
const ownershipSection = workflowSummary.ownershipLines.length > 0
|
|
411
|
+
? `\n## Ownership Enforcement\n\n${workflowSummary.ownershipLines.join('\n')}\n`
|
|
412
|
+
: '';
|
|
413
|
+
|
|
355
414
|
return `# ${role.title} — Role Prompt
|
|
356
415
|
|
|
357
416
|
You are the **${role.title}** on this project.
|
|
@@ -371,7 +430,7 @@ ${role.write_authority === 'authoritative'
|
|
|
371
430
|
? 'You may modify product files directly.'
|
|
372
431
|
: role.write_authority === 'proposed'
|
|
373
432
|
? 'You may propose changes via patches.'
|
|
374
|
-
: 'You may NOT modify product files. Only create review artifacts under `.planning/` and `.agentxchain/reviews/`.'}
|
|
433
|
+
: 'You may NOT modify product files. Only create review artifacts under `.planning/` and `.agentxchain/reviews/`.'}${primaryPhasesSection}${phaseGatesSection}${workflowArtifactsSection}${ownershipSection}
|
|
375
434
|
`;
|
|
376
435
|
}
|
|
377
436
|
|
|
@@ -454,13 +513,118 @@ function formatInitTarget(dir) {
|
|
|
454
513
|
return dir;
|
|
455
514
|
}
|
|
456
515
|
|
|
457
|
-
|
|
516
|
+
function generateWorkflowKitPlaceholder(artifact, projectName) {
|
|
517
|
+
const filename = basename(artifact.path);
|
|
518
|
+
const title = filename.replace(/\.[^.]+$/, '').replace(/[-_]/g, ' ');
|
|
519
|
+
|
|
520
|
+
if (artifact.semantics === 'section_check' && artifact.semantics_config?.required_sections?.length) {
|
|
521
|
+
const sections = artifact.semantics_config.required_sections
|
|
522
|
+
.map(s => `${s}\n\n(Content here.)\n`)
|
|
523
|
+
.join('\n');
|
|
524
|
+
return `# ${title} — ${projectName}\n\n${sections}`;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
return `# ${title} — ${projectName}\n\n(Operator fills this in.)\n`;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
function cloneJsonCompatible(value) {
|
|
531
|
+
return value == null ? value : JSON.parse(JSON.stringify(value));
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
function buildScaffoldConfigFromTemplate(template, localDevRuntime, workflowKitConfig) {
|
|
535
|
+
const blueprint = template.scaffold_blueprint || null;
|
|
536
|
+
const roles = cloneJsonCompatible(blueprint?.roles || GOVERNED_ROLES);
|
|
537
|
+
const runtimes = cloneJsonCompatible(blueprint?.runtimes || GOVERNED_RUNTIMES);
|
|
538
|
+
|
|
539
|
+
if (!blueprint || Object.values(roles).some((role) => role?.runtime === 'local-dev')) {
|
|
540
|
+
runtimes['local-dev'] = localDevRuntime;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
const routing = cloneJsonCompatible(blueprint?.routing || GOVERNED_ROUTING);
|
|
544
|
+
const gates = cloneJsonCompatible(blueprint?.gates || GOVERNED_GATES);
|
|
545
|
+
const effectiveWorkflowKitConfig = workflowKitConfig || cloneJsonCompatible(blueprint?.workflow_kit || null);
|
|
546
|
+
const prompts = Object.fromEntries(
|
|
547
|
+
Object.keys(roles).map((roleId) => [roleId, `.agentxchain/prompts/${roleId}.md`])
|
|
548
|
+
);
|
|
549
|
+
|
|
550
|
+
return {
|
|
551
|
+
roles,
|
|
552
|
+
runtimes,
|
|
553
|
+
routing,
|
|
554
|
+
gates,
|
|
555
|
+
prompts,
|
|
556
|
+
workflowKitConfig: effectiveWorkflowKitConfig,
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
function buildRoadmapPhaseTable(routing, roles) {
|
|
561
|
+
const rows = Object.entries(routing).map(([phaseKey, phaseConfig]) => {
|
|
562
|
+
const phaseName = phaseKey.replace(/_/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());
|
|
563
|
+
const entryRole = phaseConfig.entry_role;
|
|
564
|
+
const role = roles[entryRole];
|
|
565
|
+
const goal = role?.mandate || phaseName;
|
|
566
|
+
const status = phaseKey === Object.keys(routing)[0] ? 'In progress' : 'Pending';
|
|
567
|
+
return `| ${phaseName} | ${goal} | ${status} |`;
|
|
568
|
+
});
|
|
569
|
+
return `| Phase | Goal | Status |\n|-------|------|--------|\n${rows.join('\n')}\n`;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
function buildPlanningSummaryLines(template, workflowKitConfig) {
|
|
573
|
+
const lines = [
|
|
574
|
+
'PM_SIGNOFF.md / ROADMAP.md / SYSTEM_SPEC.md',
|
|
575
|
+
'acceptance-matrix.md / ship-verdict.md',
|
|
576
|
+
'RELEASE_NOTES.md',
|
|
577
|
+
];
|
|
578
|
+
const templatePlanningFiles = Array.isArray(template?.planning_artifacts)
|
|
579
|
+
? template.planning_artifacts
|
|
580
|
+
.map((artifact) => artifact?.filename)
|
|
581
|
+
.filter(Boolean)
|
|
582
|
+
: [];
|
|
583
|
+
const defaultScaffoldPaths = new Set([
|
|
584
|
+
'.planning/PM_SIGNOFF.md',
|
|
585
|
+
'.planning/ROADMAP.md',
|
|
586
|
+
'.planning/SYSTEM_SPEC.md',
|
|
587
|
+
'.planning/IMPLEMENTATION_NOTES.md',
|
|
588
|
+
'.planning/acceptance-matrix.md',
|
|
589
|
+
'.planning/ship-verdict.md',
|
|
590
|
+
'.planning/RELEASE_NOTES.md',
|
|
591
|
+
]);
|
|
592
|
+
const customWorkflowFiles = [];
|
|
593
|
+
|
|
594
|
+
if (workflowKitConfig?.phases && typeof workflowKitConfig.phases === 'object') {
|
|
595
|
+
for (const phaseConfig of Object.values(workflowKitConfig.phases)) {
|
|
596
|
+
if (!Array.isArray(phaseConfig?.artifacts)) continue;
|
|
597
|
+
for (const artifact of phaseConfig.artifacts) {
|
|
598
|
+
if (!artifact?.path || defaultScaffoldPaths.has(artifact.path)) continue;
|
|
599
|
+
customWorkflowFiles.push(basename(artifact.path));
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
if (templatePlanningFiles.length > 0) {
|
|
605
|
+
lines.push(`template: ${templatePlanningFiles.join(' / ')}`);
|
|
606
|
+
}
|
|
607
|
+
const uniqueCustomWorkflowFiles = [...new Set(customWorkflowFiles)];
|
|
608
|
+
if (uniqueCustomWorkflowFiles.length > 0) {
|
|
609
|
+
lines.push(`workflow: ${uniqueCustomWorkflowFiles.join(' / ')}`);
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
return lines;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
export function scaffoldGoverned(dir, projectName, projectId, templateId = 'generic', runtimeOptions = {}, workflowKitConfig = null) {
|
|
458
616
|
const template = loadGovernedTemplate(templateId);
|
|
459
617
|
const { runtime: localDevRuntime } = resolveGovernedLocalDevRuntime(runtimeOptions);
|
|
460
|
-
const
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
618
|
+
const scaffoldConfig = buildScaffoldConfigFromTemplate(template, localDevRuntime, workflowKitConfig);
|
|
619
|
+
const { roles, runtimes, routing, gates, prompts, workflowKitConfig: effectiveWorkflowKitConfig } = scaffoldConfig;
|
|
620
|
+
const initialPhase = Object.keys(routing)[0] || 'planning';
|
|
621
|
+
const phaseGateStatus = Object.fromEntries(
|
|
622
|
+
[...new Set(
|
|
623
|
+
Object.values(routing)
|
|
624
|
+
.map((route) => route?.exit_gate)
|
|
625
|
+
.filter(Boolean)
|
|
626
|
+
)].map((gateId) => [gateId, 'pending'])
|
|
627
|
+
);
|
|
464
628
|
const config = {
|
|
465
629
|
schema_version: '1.0',
|
|
466
630
|
template: template.id,
|
|
@@ -469,10 +633,10 @@ export function scaffoldGoverned(dir, projectName, projectId, templateId = 'gene
|
|
|
469
633
|
name: projectName,
|
|
470
634
|
default_branch: 'main'
|
|
471
635
|
},
|
|
472
|
-
roles
|
|
636
|
+
roles,
|
|
473
637
|
runtimes,
|
|
474
|
-
routing
|
|
475
|
-
gates
|
|
638
|
+
routing,
|
|
639
|
+
gates,
|
|
476
640
|
budget: {
|
|
477
641
|
per_turn_max_usd: 2.0,
|
|
478
642
|
per_run_max_usd: 50.0,
|
|
@@ -482,25 +646,23 @@ export function scaffoldGoverned(dir, projectName, projectId, templateId = 'gene
|
|
|
482
646
|
talk_strategy: 'append_only',
|
|
483
647
|
history_strategy: 'jsonl_append_only'
|
|
484
648
|
},
|
|
485
|
-
prompts
|
|
486
|
-
pm: '.agentxchain/prompts/pm.md',
|
|
487
|
-
dev: '.agentxchain/prompts/dev.md',
|
|
488
|
-
qa: '.agentxchain/prompts/qa.md',
|
|
489
|
-
eng_director: '.agentxchain/prompts/eng_director.md'
|
|
490
|
-
},
|
|
649
|
+
prompts,
|
|
491
650
|
rules: {
|
|
492
651
|
challenge_required: true,
|
|
493
652
|
max_turn_retries: 2,
|
|
494
653
|
max_deadlock_cycles: 2
|
|
495
654
|
}
|
|
496
655
|
};
|
|
656
|
+
if (effectiveWorkflowKitConfig) {
|
|
657
|
+
config.workflow_kit = effectiveWorkflowKitConfig;
|
|
658
|
+
}
|
|
497
659
|
|
|
498
660
|
const state = {
|
|
499
661
|
schema_version: '1.1',
|
|
500
662
|
run_id: null,
|
|
501
663
|
project_id: projectId,
|
|
502
664
|
status: 'idle',
|
|
503
|
-
phase:
|
|
665
|
+
phase: initialPhase,
|
|
504
666
|
accepted_integration_ref: null,
|
|
505
667
|
active_turns: {},
|
|
506
668
|
turn_sequence: 0,
|
|
@@ -510,11 +672,7 @@ export function scaffoldGoverned(dir, projectName, projectId, templateId = 'gene
|
|
|
510
672
|
escalation: null,
|
|
511
673
|
queued_phase_transition: null,
|
|
512
674
|
queued_run_completion: null,
|
|
513
|
-
phase_gate_status:
|
|
514
|
-
planning_signoff: 'pending',
|
|
515
|
-
implementation_complete: 'pending',
|
|
516
|
-
qa_ship_verdict: 'pending'
|
|
517
|
-
},
|
|
675
|
+
phase_gate_status: phaseGateStatus,
|
|
518
676
|
budget_reservations: {},
|
|
519
677
|
budget_status: {
|
|
520
678
|
spent_usd: 0,
|
|
@@ -536,15 +694,19 @@ export function scaffoldGoverned(dir, projectName, projectId, templateId = 'gene
|
|
|
536
694
|
writeFileSync(join(dir, '.agentxchain', 'decision-ledger.jsonl'), '');
|
|
537
695
|
|
|
538
696
|
// Prompt templates
|
|
539
|
-
for (const [roleId, role] of Object.entries(
|
|
540
|
-
const basePrompt = buildGovernedPrompt(roleId, role
|
|
697
|
+
for (const [roleId, role] of Object.entries(roles)) {
|
|
698
|
+
const basePrompt = buildGovernedPrompt(roleId, role, {
|
|
699
|
+
routing,
|
|
700
|
+
gates,
|
|
701
|
+
workflowKitConfig: effectiveWorkflowKitConfig,
|
|
702
|
+
});
|
|
541
703
|
const prompt = appendPromptOverride(basePrompt, template.prompt_overrides?.[roleId]);
|
|
542
704
|
writeFileSync(join(dir, '.agentxchain', 'prompts', `${roleId}.md`), prompt);
|
|
543
705
|
}
|
|
544
706
|
|
|
545
707
|
// Planning artifacts
|
|
546
708
|
writeFileSync(join(dir, '.planning', 'PM_SIGNOFF.md'), `# PM Signoff — ${projectName}\n\nApproved: NO\n\n> This scaffold starts blocked on purpose. Change this to \`Approved: YES\` only after a human reviews the planning artifacts and is ready to open the planning gate.\n\n## Discovery Checklist\n- [ ] Target user defined\n- [ ] Core pain point defined\n- [ ] Core workflow defined\n- [ ] MVP scope defined\n- [ ] Out-of-scope list defined\n- [ ] Success metric defined\n\n## Notes for team\n(PM and human add final kickoff notes here.)\n`);
|
|
547
|
-
writeFileSync(join(dir, '.planning', 'ROADMAP.md'), `# Roadmap — ${projectName}\n\n## Phases\n\n
|
|
709
|
+
writeFileSync(join(dir, '.planning', 'ROADMAP.md'), `# Roadmap — ${projectName}\n\n## Phases\n\n${buildRoadmapPhaseTable(routing, roles)}`);
|
|
548
710
|
writeFileSync(join(dir, '.planning', 'SYSTEM_SPEC.md'), buildSystemSpecContent(projectName, template.system_spec_overlay));
|
|
549
711
|
writeFileSync(join(dir, '.planning', 'IMPLEMENTATION_NOTES.md'), `# Implementation Notes — ${projectName}\n\n## Changes\n\n(Dev fills this during implementation)\n\n## Verification\n\n(Dev fills this during implementation)\n\n## Unresolved Follow-ups\n\n(Dev lists any known gaps, tech debt, or follow-up items here.)\n`);
|
|
550
712
|
const baseAcceptanceMatrix = `# Acceptance Matrix — ${projectName}\n\n| Req # | Requirement | Acceptance criteria | Test status | Last tested | Status |\n|-------|-------------|-------------------|-------------|-------------|--------|\n| (QA fills this from ROADMAP.md) | | | | | |\n`;
|
|
@@ -561,6 +723,37 @@ export function scaffoldGoverned(dir, projectName, projectId, templateId = 'gene
|
|
|
561
723
|
);
|
|
562
724
|
}
|
|
563
725
|
|
|
726
|
+
// Workflow-kit custom artifacts — only scaffold files from explicit workflow_kit config
|
|
727
|
+
// that are not already handled by the default scaffold above
|
|
728
|
+
if (effectiveWorkflowKitConfig && effectiveWorkflowKitConfig.phases && typeof effectiveWorkflowKitConfig.phases === 'object') {
|
|
729
|
+
const defaultScaffoldPaths = new Set([
|
|
730
|
+
'.planning/PM_SIGNOFF.md',
|
|
731
|
+
'.planning/ROADMAP.md',
|
|
732
|
+
'.planning/SYSTEM_SPEC.md',
|
|
733
|
+
'.planning/IMPLEMENTATION_NOTES.md',
|
|
734
|
+
'.planning/acceptance-matrix.md',
|
|
735
|
+
'.planning/ship-verdict.md',
|
|
736
|
+
'.planning/RELEASE_NOTES.md',
|
|
737
|
+
]);
|
|
738
|
+
|
|
739
|
+
for (const phaseConfig of Object.values(effectiveWorkflowKitConfig.phases)) {
|
|
740
|
+
if (!Array.isArray(phaseConfig.artifacts)) continue;
|
|
741
|
+
for (const artifact of phaseConfig.artifacts) {
|
|
742
|
+
if (!artifact.path || defaultScaffoldPaths.has(artifact.path)) continue;
|
|
743
|
+
const absPath = join(dir, artifact.path);
|
|
744
|
+
if (existsSync(absPath)) continue;
|
|
745
|
+
|
|
746
|
+
// Ensure parent directory exists
|
|
747
|
+
const parentDir = dirname(absPath);
|
|
748
|
+
if (!existsSync(parentDir)) mkdirSync(parentDir, { recursive: true });
|
|
749
|
+
|
|
750
|
+
// Generate placeholder content based on semantics type
|
|
751
|
+
const content = generateWorkflowKitPlaceholder(artifact, projectName);
|
|
752
|
+
writeFileSync(absPath, content);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
564
757
|
// TALK.md
|
|
565
758
|
writeFileSync(join(dir, 'TALK.md'), `# ${projectName} — Team Talk File\n\nCanonical human-readable handoff log for all agents.\n\n---\n\n`);
|
|
566
759
|
|
|
@@ -584,6 +777,7 @@ export function scaffoldGoverned(dir, projectName, projectId, templateId = 'gene
|
|
|
584
777
|
async function initGoverned(opts) {
|
|
585
778
|
let projectName, folderName;
|
|
586
779
|
const templateId = opts.template || 'generic';
|
|
780
|
+
let selectedTemplate;
|
|
587
781
|
let explicitDir;
|
|
588
782
|
|
|
589
783
|
try {
|
|
@@ -604,6 +798,7 @@ async function initGoverned(opts) {
|
|
|
604
798
|
console.error(' web-app Governed scaffold for a web application');
|
|
605
799
|
process.exit(1);
|
|
606
800
|
}
|
|
801
|
+
selectedTemplate = loadGovernedTemplate(templateId);
|
|
607
802
|
|
|
608
803
|
if (opts.yes) {
|
|
609
804
|
projectName = explicitDir
|
|
@@ -662,47 +857,68 @@ async function initGoverned(opts) {
|
|
|
662
857
|
|
|
663
858
|
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
664
859
|
|
|
665
|
-
|
|
860
|
+
// If reinitializing a project that has explicit workflow_kit config, preserve it for scaffold
|
|
861
|
+
let workflowKitConfig = null;
|
|
862
|
+
const existingConfigPath = join(dir, CONFIG_FILE);
|
|
863
|
+
if (existsSync(existingConfigPath)) {
|
|
864
|
+
try {
|
|
865
|
+
const existing = JSON.parse(readFileSync(existingConfigPath, 'utf8'));
|
|
866
|
+
if (existing.workflow_kit && typeof existing.workflow_kit === 'object' && Object.keys(existing.workflow_kit).length > 0) {
|
|
867
|
+
workflowKitConfig = existing.workflow_kit;
|
|
868
|
+
}
|
|
869
|
+
} catch {
|
|
870
|
+
// Ignore parse errors — scaffold will overwrite the config anyway
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
const { config } = scaffoldGoverned(dir, projectName, projectId, templateId, opts, workflowKitConfig);
|
|
666
875
|
|
|
667
876
|
console.log('');
|
|
668
877
|
console.log(chalk.green(` ✓ Created governed project ${chalk.bold(targetLabel)}/`));
|
|
669
878
|
console.log('');
|
|
879
|
+
const promptRoleIds = Object.keys(config.roles);
|
|
880
|
+
const phaseNames = Object.keys(config.routing);
|
|
670
881
|
console.log(` ${chalk.dim('├──')} agentxchain.json ${chalk.dim('(governed)')}`);
|
|
671
882
|
console.log(` ${chalk.dim('├──')} .agentxchain/`);
|
|
672
883
|
console.log(` ${chalk.dim('│')} ${chalk.dim('├──')} state.json / history.jsonl / decision-ledger.jsonl`);
|
|
673
884
|
console.log(` ${chalk.dim('│')} ${chalk.dim('├──')} staging/`);
|
|
674
|
-
console.log(` ${chalk.dim('│')} ${chalk.dim('├──')} prompts/ ${chalk.dim('
|
|
885
|
+
console.log(` ${chalk.dim('│')} ${chalk.dim('├──')} prompts/ ${chalk.dim(`(${promptRoleIds.join(', ')})`)}`);
|
|
675
886
|
console.log(` ${chalk.dim('│')} ${chalk.dim('├──')} reviews/`);
|
|
676
887
|
console.log(` ${chalk.dim('│')} ${chalk.dim('└──')} dispatch/`);
|
|
677
888
|
console.log(` ${chalk.dim('├──')} .planning/`);
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
889
|
+
const planningSummaryLines = buildPlanningSummaryLines(selectedTemplate, config.workflow_kit);
|
|
890
|
+
for (const [index, line] of planningSummaryLines.entries()) {
|
|
891
|
+
const branch = index === planningSummaryLines.length - 1 ? '└──' : '├──';
|
|
892
|
+
console.log(` ${chalk.dim('│')} ${chalk.dim(branch)} ${line}`);
|
|
893
|
+
}
|
|
681
894
|
console.log(` ${chalk.dim('└──')} TALK.md`);
|
|
682
895
|
console.log('');
|
|
683
|
-
console.log(` ${chalk.dim('Roles:')}
|
|
684
|
-
console.log(` ${chalk.dim('Phases:')}
|
|
896
|
+
console.log(` ${chalk.dim('Roles:')} ${promptRoleIds.join(', ')}`);
|
|
897
|
+
console.log(` ${chalk.dim('Phases:')} ${phaseNames.join(' → ')} ${chalk.dim(selectedTemplate.scaffold_blueprint ? '(template-defined; edit routing in agentxchain.json to customize)' : '(default; extend via routing in agentxchain.json)')}`);
|
|
685
898
|
console.log(` ${chalk.dim('Template:')} ${templateId}`);
|
|
686
899
|
console.log(` ${chalk.dim('Dev runtime:')} ${formatGovernedRuntimeCommand(localDevRuntime)} ${chalk.dim(`(${localDevRuntime.prompt_transport})`)}`);
|
|
687
900
|
console.log(` ${chalk.dim('Protocol:')} governed convergence`);
|
|
688
901
|
console.log('');
|
|
689
902
|
|
|
690
903
|
// Readiness hint: tell user which roles work immediately vs which need API keys
|
|
691
|
-
const allRuntimes =
|
|
692
|
-
const
|
|
693
|
-
.filter(([,
|
|
694
|
-
.map(([
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
904
|
+
const allRuntimes = config.runtimes;
|
|
905
|
+
const manualRoleIds = Object.entries(config.roles)
|
|
906
|
+
.filter(([, role]) => allRuntimes[role.runtime]?.type === 'manual')
|
|
907
|
+
.map(([roleId]) => roleId);
|
|
908
|
+
const rolesNeedingKeys = Object.entries(config.roles)
|
|
909
|
+
.filter(([, role]) => Boolean(allRuntimes[role.runtime]?.auth_env))
|
|
910
|
+
.map(([roleId, role]) => ({ roleId, env: allRuntimes[role.runtime].auth_env }));
|
|
911
|
+
if (rolesNeedingKeys.length > 0) {
|
|
912
|
+
const envVars = [...new Set(rolesNeedingKeys.map((r) => r.env))];
|
|
913
|
+
const roleNames = rolesNeedingKeys.map((r) => r.roleId);
|
|
698
914
|
const hasKeys = envVars.every(v => process.env[v]);
|
|
699
915
|
if (hasKeys) {
|
|
700
916
|
console.log(` ${chalk.green('Ready:')} all runtimes configured (${envVars.join(', ')} detected)`);
|
|
701
917
|
} else {
|
|
702
|
-
console.log(` ${chalk.yellow('Mixed-mode:')}
|
|
918
|
+
console.log(` ${chalk.yellow('Mixed-mode:')} ${manualRoleIds.join(', ')} work immediately (manual).`);
|
|
703
919
|
console.log(` ${chalk.yellow(' ')}${roleNames.join(', ')} need ${chalk.bold(envVars.join(', '))} to dispatch automatically.`);
|
|
704
920
|
console.log(` ${chalk.yellow(' ')}Without it, those turns fall back to manual input.`);
|
|
705
|
-
if (allRuntimes['manual-qa']) {
|
|
921
|
+
if (config.roles?.qa?.runtime === 'api-qa' && allRuntimes['manual-qa']) {
|
|
706
922
|
console.log(` ${chalk.yellow(' ')}No-key QA path: change ${chalk.bold('roles.qa.runtime')} from ${chalk.bold('"api-qa"')} to ${chalk.bold('"manual-qa"')} in ${chalk.bold('agentxchain.json')}.`);
|
|
707
923
|
}
|
|
708
924
|
}
|
|
@@ -10,6 +10,7 @@ export function templateListCommand(opts) {
|
|
|
10
10
|
description: t.description,
|
|
11
11
|
planning_artifacts: (t.planning_artifacts || []).map((a) => a.filename),
|
|
12
12
|
prompt_overrides: Object.keys(t.prompt_overrides || {}),
|
|
13
|
+
scaffold_blueprint_roles: Object.keys(t.scaffold_blueprint?.roles || {}),
|
|
13
14
|
acceptance_hints: t.acceptance_hints || [],
|
|
14
15
|
}));
|
|
15
16
|
console.log(JSON.stringify(output, null, 2));
|
|
@@ -27,6 +28,9 @@ export function templateListCommand(opts) {
|
|
|
27
28
|
if (t.prompt_overrides && Object.keys(t.prompt_overrides).length > 0) {
|
|
28
29
|
console.log(` Prompt overrides: ${Object.keys(t.prompt_overrides).join(', ')}`);
|
|
29
30
|
}
|
|
31
|
+
if (t.scaffold_blueprint?.roles && Object.keys(t.scaffold_blueprint.roles).length > 0) {
|
|
32
|
+
console.log(` Scaffold roles: ${Object.keys(t.scaffold_blueprint.roles).join(', ')}`);
|
|
33
|
+
}
|
|
30
34
|
console.log('');
|
|
31
35
|
}
|
|
32
36
|
console.log(chalk.dim(` Usage: agentxchain template set <id>\n`));
|
|
@@ -64,6 +64,12 @@ export async function templateSetCommand(templateId, opts) {
|
|
|
64
64
|
|
|
65
65
|
// ── Load manifest ─────────────────────────────────────────────────────
|
|
66
66
|
const manifest = loadGovernedTemplate(templateId);
|
|
67
|
+
if (manifest.scaffold_blueprint) {
|
|
68
|
+
console.error(chalk.red(` Error: Template "${templateId}" defines a custom governed team blueprint.`));
|
|
69
|
+
console.error(chalk.yellow(` Use ${chalk.bold(`agentxchain init --governed --template ${templateId}`)} for new repos.`));
|
|
70
|
+
console.error(chalk.yellow(' Retrofitting an existing repo to a blueprint-backed team is deferred until a dedicated migrator exists.'));
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
67
73
|
const projectName = config.project?.name || 'Untitled';
|
|
68
74
|
|
|
69
75
|
// ── Build mutation plan ───────────────────────────────────────────────
|