agentxchain 2.25.2 → 2.27.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 +212 -44
- package/src/commands/template-list.js +4 -0
- package/src/commands/template-set.js +6 -0
- package/src/lib/adapters/manual-adapter.js +1 -1
- package/src/lib/gate-evaluator.js +26 -2
- package/src/lib/governed-state.js +3 -1
- package/src/lib/governed-templates.js +47 -3
- package/src/lib/normalized-config.js +13 -2
- 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
|
|
|
@@ -468,13 +527,115 @@ function generateWorkflowKitPlaceholder(artifact, projectName) {
|
|
|
468
527
|
return `# ${title} — ${projectName}\n\n(Operator fills this in.)\n`;
|
|
469
528
|
}
|
|
470
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
|
+
const PHASE_DISPLAY_NAMES = Object.freeze({
|
|
561
|
+
qa: 'QA',
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
function formatPhaseDisplayName(phaseKey) {
|
|
565
|
+
if (PHASE_DISPLAY_NAMES[phaseKey]) {
|
|
566
|
+
return PHASE_DISPLAY_NAMES[phaseKey];
|
|
567
|
+
}
|
|
568
|
+
return phaseKey.replace(/_/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
function buildRoadmapPhaseTable(routing, roles) {
|
|
572
|
+
const rows = Object.entries(routing).map(([phaseKey, phaseConfig]) => {
|
|
573
|
+
const phaseName = formatPhaseDisplayName(phaseKey);
|
|
574
|
+
const entryRole = phaseConfig.entry_role;
|
|
575
|
+
const role = roles[entryRole];
|
|
576
|
+
const goal = role?.mandate || phaseName;
|
|
577
|
+
const status = phaseKey === Object.keys(routing)[0] ? 'In progress' : 'Pending';
|
|
578
|
+
return `| ${phaseName} | ${goal} | ${status} |`;
|
|
579
|
+
});
|
|
580
|
+
return `| Phase | Goal | Status |\n|-------|------|--------|\n${rows.join('\n')}\n`;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
function buildPlanningSummaryLines(template, workflowKitConfig) {
|
|
584
|
+
const lines = [
|
|
585
|
+
'PM_SIGNOFF.md / ROADMAP.md / SYSTEM_SPEC.md',
|
|
586
|
+
'acceptance-matrix.md / ship-verdict.md',
|
|
587
|
+
'RELEASE_NOTES.md',
|
|
588
|
+
];
|
|
589
|
+
const templatePlanningFiles = Array.isArray(template?.planning_artifacts)
|
|
590
|
+
? template.planning_artifacts
|
|
591
|
+
.map((artifact) => artifact?.filename)
|
|
592
|
+
.filter(Boolean)
|
|
593
|
+
: [];
|
|
594
|
+
const defaultScaffoldPaths = new Set([
|
|
595
|
+
'.planning/PM_SIGNOFF.md',
|
|
596
|
+
'.planning/ROADMAP.md',
|
|
597
|
+
'.planning/SYSTEM_SPEC.md',
|
|
598
|
+
'.planning/IMPLEMENTATION_NOTES.md',
|
|
599
|
+
'.planning/acceptance-matrix.md',
|
|
600
|
+
'.planning/ship-verdict.md',
|
|
601
|
+
'.planning/RELEASE_NOTES.md',
|
|
602
|
+
]);
|
|
603
|
+
const customWorkflowFiles = [];
|
|
604
|
+
|
|
605
|
+
if (workflowKitConfig?.phases && typeof workflowKitConfig.phases === 'object') {
|
|
606
|
+
for (const phaseConfig of Object.values(workflowKitConfig.phases)) {
|
|
607
|
+
if (!Array.isArray(phaseConfig?.artifacts)) continue;
|
|
608
|
+
for (const artifact of phaseConfig.artifacts) {
|
|
609
|
+
if (!artifact?.path || defaultScaffoldPaths.has(artifact.path)) continue;
|
|
610
|
+
customWorkflowFiles.push(basename(artifact.path));
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
if (templatePlanningFiles.length > 0) {
|
|
616
|
+
lines.push(`template: ${templatePlanningFiles.join(' / ')}`);
|
|
617
|
+
}
|
|
618
|
+
const uniqueCustomWorkflowFiles = [...new Set(customWorkflowFiles)];
|
|
619
|
+
if (uniqueCustomWorkflowFiles.length > 0) {
|
|
620
|
+
lines.push(`workflow: ${uniqueCustomWorkflowFiles.join(' / ')}`);
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
return lines;
|
|
624
|
+
}
|
|
625
|
+
|
|
471
626
|
export function scaffoldGoverned(dir, projectName, projectId, templateId = 'generic', runtimeOptions = {}, workflowKitConfig = null) {
|
|
472
627
|
const template = loadGovernedTemplate(templateId);
|
|
473
628
|
const { runtime: localDevRuntime } = resolveGovernedLocalDevRuntime(runtimeOptions);
|
|
474
|
-
const
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
629
|
+
const scaffoldConfig = buildScaffoldConfigFromTemplate(template, localDevRuntime, workflowKitConfig);
|
|
630
|
+
const { roles, runtimes, routing, gates, prompts, workflowKitConfig: effectiveWorkflowKitConfig } = scaffoldConfig;
|
|
631
|
+
const initialPhase = Object.keys(routing)[0] || 'planning';
|
|
632
|
+
const phaseGateStatus = Object.fromEntries(
|
|
633
|
+
[...new Set(
|
|
634
|
+
Object.values(routing)
|
|
635
|
+
.map((route) => route?.exit_gate)
|
|
636
|
+
.filter(Boolean)
|
|
637
|
+
)].map((gateId) => [gateId, 'pending'])
|
|
638
|
+
);
|
|
478
639
|
const config = {
|
|
479
640
|
schema_version: '1.0',
|
|
480
641
|
template: template.id,
|
|
@@ -483,10 +644,10 @@ export function scaffoldGoverned(dir, projectName, projectId, templateId = 'gene
|
|
|
483
644
|
name: projectName,
|
|
484
645
|
default_branch: 'main'
|
|
485
646
|
},
|
|
486
|
-
roles
|
|
647
|
+
roles,
|
|
487
648
|
runtimes,
|
|
488
|
-
routing
|
|
489
|
-
gates
|
|
649
|
+
routing,
|
|
650
|
+
gates,
|
|
490
651
|
budget: {
|
|
491
652
|
per_turn_max_usd: 2.0,
|
|
492
653
|
per_run_max_usd: 50.0,
|
|
@@ -496,25 +657,23 @@ export function scaffoldGoverned(dir, projectName, projectId, templateId = 'gene
|
|
|
496
657
|
talk_strategy: 'append_only',
|
|
497
658
|
history_strategy: 'jsonl_append_only'
|
|
498
659
|
},
|
|
499
|
-
prompts
|
|
500
|
-
pm: '.agentxchain/prompts/pm.md',
|
|
501
|
-
dev: '.agentxchain/prompts/dev.md',
|
|
502
|
-
qa: '.agentxchain/prompts/qa.md',
|
|
503
|
-
eng_director: '.agentxchain/prompts/eng_director.md'
|
|
504
|
-
},
|
|
660
|
+
prompts,
|
|
505
661
|
rules: {
|
|
506
662
|
challenge_required: true,
|
|
507
663
|
max_turn_retries: 2,
|
|
508
664
|
max_deadlock_cycles: 2
|
|
509
665
|
}
|
|
510
666
|
};
|
|
667
|
+
if (effectiveWorkflowKitConfig) {
|
|
668
|
+
config.workflow_kit = effectiveWorkflowKitConfig;
|
|
669
|
+
}
|
|
511
670
|
|
|
512
671
|
const state = {
|
|
513
672
|
schema_version: '1.1',
|
|
514
673
|
run_id: null,
|
|
515
674
|
project_id: projectId,
|
|
516
675
|
status: 'idle',
|
|
517
|
-
phase:
|
|
676
|
+
phase: initialPhase,
|
|
518
677
|
accepted_integration_ref: null,
|
|
519
678
|
active_turns: {},
|
|
520
679
|
turn_sequence: 0,
|
|
@@ -524,11 +683,7 @@ export function scaffoldGoverned(dir, projectName, projectId, templateId = 'gene
|
|
|
524
683
|
escalation: null,
|
|
525
684
|
queued_phase_transition: null,
|
|
526
685
|
queued_run_completion: null,
|
|
527
|
-
phase_gate_status:
|
|
528
|
-
planning_signoff: 'pending',
|
|
529
|
-
implementation_complete: 'pending',
|
|
530
|
-
qa_ship_verdict: 'pending'
|
|
531
|
-
},
|
|
686
|
+
phase_gate_status: phaseGateStatus,
|
|
532
687
|
budget_reservations: {},
|
|
533
688
|
budget_status: {
|
|
534
689
|
spent_usd: 0,
|
|
@@ -550,15 +705,19 @@ export function scaffoldGoverned(dir, projectName, projectId, templateId = 'gene
|
|
|
550
705
|
writeFileSync(join(dir, '.agentxchain', 'decision-ledger.jsonl'), '');
|
|
551
706
|
|
|
552
707
|
// Prompt templates
|
|
553
|
-
for (const [roleId, role] of Object.entries(
|
|
554
|
-
const basePrompt = buildGovernedPrompt(roleId, role
|
|
708
|
+
for (const [roleId, role] of Object.entries(roles)) {
|
|
709
|
+
const basePrompt = buildGovernedPrompt(roleId, role, {
|
|
710
|
+
routing,
|
|
711
|
+
gates,
|
|
712
|
+
workflowKitConfig: effectiveWorkflowKitConfig,
|
|
713
|
+
});
|
|
555
714
|
const prompt = appendPromptOverride(basePrompt, template.prompt_overrides?.[roleId]);
|
|
556
715
|
writeFileSync(join(dir, '.agentxchain', 'prompts', `${roleId}.md`), prompt);
|
|
557
716
|
}
|
|
558
717
|
|
|
559
718
|
// Planning artifacts
|
|
560
719
|
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`);
|
|
561
|
-
writeFileSync(join(dir, '.planning', 'ROADMAP.md'), `# Roadmap — ${projectName}\n\n## Phases\n\n
|
|
720
|
+
writeFileSync(join(dir, '.planning', 'ROADMAP.md'), `# Roadmap — ${projectName}\n\n## Phases\n\n${buildRoadmapPhaseTable(routing, roles)}`);
|
|
562
721
|
writeFileSync(join(dir, '.planning', 'SYSTEM_SPEC.md'), buildSystemSpecContent(projectName, template.system_spec_overlay));
|
|
563
722
|
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`);
|
|
564
723
|
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`;
|
|
@@ -577,7 +736,7 @@ export function scaffoldGoverned(dir, projectName, projectId, templateId = 'gene
|
|
|
577
736
|
|
|
578
737
|
// Workflow-kit custom artifacts — only scaffold files from explicit workflow_kit config
|
|
579
738
|
// that are not already handled by the default scaffold above
|
|
580
|
-
if (
|
|
739
|
+
if (effectiveWorkflowKitConfig && effectiveWorkflowKitConfig.phases && typeof effectiveWorkflowKitConfig.phases === 'object') {
|
|
581
740
|
const defaultScaffoldPaths = new Set([
|
|
582
741
|
'.planning/PM_SIGNOFF.md',
|
|
583
742
|
'.planning/ROADMAP.md',
|
|
@@ -588,7 +747,7 @@ export function scaffoldGoverned(dir, projectName, projectId, templateId = 'gene
|
|
|
588
747
|
'.planning/RELEASE_NOTES.md',
|
|
589
748
|
]);
|
|
590
749
|
|
|
591
|
-
for (const phaseConfig of Object.values(
|
|
750
|
+
for (const phaseConfig of Object.values(effectiveWorkflowKitConfig.phases)) {
|
|
592
751
|
if (!Array.isArray(phaseConfig.artifacts)) continue;
|
|
593
752
|
for (const artifact of phaseConfig.artifacts) {
|
|
594
753
|
if (!artifact.path || defaultScaffoldPaths.has(artifact.path)) continue;
|
|
@@ -629,6 +788,7 @@ export function scaffoldGoverned(dir, projectName, projectId, templateId = 'gene
|
|
|
629
788
|
async function initGoverned(opts) {
|
|
630
789
|
let projectName, folderName;
|
|
631
790
|
const templateId = opts.template || 'generic';
|
|
791
|
+
let selectedTemplate;
|
|
632
792
|
let explicitDir;
|
|
633
793
|
|
|
634
794
|
try {
|
|
@@ -649,6 +809,7 @@ async function initGoverned(opts) {
|
|
|
649
809
|
console.error(' web-app Governed scaffold for a web application');
|
|
650
810
|
process.exit(1);
|
|
651
811
|
}
|
|
812
|
+
selectedTemplate = loadGovernedTemplate(templateId);
|
|
652
813
|
|
|
653
814
|
if (opts.yes) {
|
|
654
815
|
projectName = explicitDir
|
|
@@ -721,47 +882,54 @@ async function initGoverned(opts) {
|
|
|
721
882
|
}
|
|
722
883
|
}
|
|
723
884
|
|
|
724
|
-
scaffoldGoverned(dir, projectName, projectId, templateId, opts, workflowKitConfig);
|
|
885
|
+
const { config } = scaffoldGoverned(dir, projectName, projectId, templateId, opts, workflowKitConfig);
|
|
725
886
|
|
|
726
887
|
console.log('');
|
|
727
888
|
console.log(chalk.green(` ✓ Created governed project ${chalk.bold(targetLabel)}/`));
|
|
728
889
|
console.log('');
|
|
890
|
+
const promptRoleIds = Object.keys(config.roles);
|
|
891
|
+
const phaseNames = Object.keys(config.routing);
|
|
729
892
|
console.log(` ${chalk.dim('├──')} agentxchain.json ${chalk.dim('(governed)')}`);
|
|
730
893
|
console.log(` ${chalk.dim('├──')} .agentxchain/`);
|
|
731
894
|
console.log(` ${chalk.dim('│')} ${chalk.dim('├──')} state.json / history.jsonl / decision-ledger.jsonl`);
|
|
732
895
|
console.log(` ${chalk.dim('│')} ${chalk.dim('├──')} staging/`);
|
|
733
|
-
console.log(` ${chalk.dim('│')} ${chalk.dim('├──')} prompts/ ${chalk.dim('
|
|
896
|
+
console.log(` ${chalk.dim('│')} ${chalk.dim('├──')} prompts/ ${chalk.dim(`(${promptRoleIds.join(', ')})`)}`);
|
|
734
897
|
console.log(` ${chalk.dim('│')} ${chalk.dim('├──')} reviews/`);
|
|
735
898
|
console.log(` ${chalk.dim('│')} ${chalk.dim('└──')} dispatch/`);
|
|
736
899
|
console.log(` ${chalk.dim('├──')} .planning/`);
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
900
|
+
const planningSummaryLines = buildPlanningSummaryLines(selectedTemplate, config.workflow_kit);
|
|
901
|
+
for (const [index, line] of planningSummaryLines.entries()) {
|
|
902
|
+
const branch = index === planningSummaryLines.length - 1 ? '└──' : '├──';
|
|
903
|
+
console.log(` ${chalk.dim('│')} ${chalk.dim(branch)} ${line}`);
|
|
904
|
+
}
|
|
740
905
|
console.log(` ${chalk.dim('└──')} TALK.md`);
|
|
741
906
|
console.log('');
|
|
742
|
-
console.log(` ${chalk.dim('Roles:')}
|
|
743
|
-
console.log(` ${chalk.dim('Phases:')}
|
|
907
|
+
console.log(` ${chalk.dim('Roles:')} ${promptRoleIds.join(', ')}`);
|
|
908
|
+
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)')}`);
|
|
744
909
|
console.log(` ${chalk.dim('Template:')} ${templateId}`);
|
|
745
910
|
console.log(` ${chalk.dim('Dev runtime:')} ${formatGovernedRuntimeCommand(localDevRuntime)} ${chalk.dim(`(${localDevRuntime.prompt_transport})`)}`);
|
|
746
911
|
console.log(` ${chalk.dim('Protocol:')} governed convergence`);
|
|
747
912
|
console.log('');
|
|
748
913
|
|
|
749
914
|
// Readiness hint: tell user which roles work immediately vs which need API keys
|
|
750
|
-
const allRuntimes =
|
|
751
|
-
const
|
|
752
|
-
.filter(([,
|
|
753
|
-
.map(([
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
915
|
+
const allRuntimes = config.runtimes;
|
|
916
|
+
const manualRoleIds = Object.entries(config.roles)
|
|
917
|
+
.filter(([, role]) => allRuntimes[role.runtime]?.type === 'manual')
|
|
918
|
+
.map(([roleId]) => roleId);
|
|
919
|
+
const rolesNeedingKeys = Object.entries(config.roles)
|
|
920
|
+
.filter(([, role]) => Boolean(allRuntimes[role.runtime]?.auth_env))
|
|
921
|
+
.map(([roleId, role]) => ({ roleId, env: allRuntimes[role.runtime].auth_env }));
|
|
922
|
+
if (rolesNeedingKeys.length > 0) {
|
|
923
|
+
const envVars = [...new Set(rolesNeedingKeys.map((r) => r.env))];
|
|
924
|
+
const roleNames = rolesNeedingKeys.map((r) => r.roleId);
|
|
757
925
|
const hasKeys = envVars.every(v => process.env[v]);
|
|
758
926
|
if (hasKeys) {
|
|
759
927
|
console.log(` ${chalk.green('Ready:')} all runtimes configured (${envVars.join(', ')} detected)`);
|
|
760
928
|
} else {
|
|
761
|
-
console.log(` ${chalk.yellow('Mixed-mode:')}
|
|
929
|
+
console.log(` ${chalk.yellow('Mixed-mode:')} ${manualRoleIds.join(', ')} work immediately (manual).`);
|
|
762
930
|
console.log(` ${chalk.yellow(' ')}${roleNames.join(', ')} need ${chalk.bold(envVars.join(', '))} to dispatch automatically.`);
|
|
763
931
|
console.log(` ${chalk.yellow(' ')}Without it, those turns fall back to manual input.`);
|
|
764
|
-
if (allRuntimes['manual-qa']) {
|
|
932
|
+
if (config.roles?.qa?.runtime === 'api-qa' && allRuntimes['manual-qa']) {
|
|
765
933
|
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')}.`);
|
|
766
934
|
}
|
|
767
935
|
}
|
|
@@ -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 ───────────────────────────────────────────────
|
|
@@ -105,7 +105,7 @@ function getPhaseGateHints(phase, roleId, config) {
|
|
|
105
105
|
hints.push('.planning/IMPLEMENTATION_NOTES.md — record what you built and how to verify');
|
|
106
106
|
} else if (phase === 'qa' && (roleId === 'qa' || roleId === 'human')) {
|
|
107
107
|
hints.push('.planning/acceptance-matrix.md — mark each requirement PASS/FAIL');
|
|
108
|
-
hints.push('.planning/ship-verdict.md — change "## Verdict: PENDING" → "## Verdict:
|
|
108
|
+
hints.push('.planning/ship-verdict.md — change "## Verdict: PENDING" → "## Verdict: YES"');
|
|
109
109
|
hints.push('.planning/RELEASE_NOTES.md — user impact, verification summary, upgrade notes');
|
|
110
110
|
}
|
|
111
111
|
|
|
@@ -54,10 +54,15 @@ function buildEffectiveGateArtifacts(config, gateDef, phase) {
|
|
|
54
54
|
required: false,
|
|
55
55
|
useLegacySemantics: false,
|
|
56
56
|
semanticChecks: [],
|
|
57
|
+
owned_by: null,
|
|
57
58
|
};
|
|
58
59
|
|
|
59
60
|
existing.required = existing.required || artifact.required !== false;
|
|
60
61
|
|
|
62
|
+
if (artifact.owned_by && typeof artifact.owned_by === 'string') {
|
|
63
|
+
existing.owned_by = artifact.owned_by;
|
|
64
|
+
}
|
|
65
|
+
|
|
61
66
|
if (artifact.semantics) {
|
|
62
67
|
const legacySemanticId = existing.useLegacySemantics ? getSemanticIdForPath(artifact.path) : null;
|
|
63
68
|
if (artifact.semantics !== legacySemanticId) {
|
|
@@ -87,7 +92,17 @@ function prefixSemanticReason(filePath, reason) {
|
|
|
87
92
|
return `${filePath}: ${reason}`;
|
|
88
93
|
}
|
|
89
94
|
|
|
90
|
-
function
|
|
95
|
+
function hasRoleParticipationInPhase(state, phase, roleId) {
|
|
96
|
+
const history = state?.history;
|
|
97
|
+
if (!Array.isArray(history)) {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
return history.some(
|
|
101
|
+
turn => turn.phase === phase && turn.role === roleId,
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function evaluateGateArtifacts({ root, config, gateDef, phase, result, state }) {
|
|
91
106
|
const failures = [];
|
|
92
107
|
const artifacts = buildEffectiveGateArtifacts(config, gateDef, phase);
|
|
93
108
|
|
|
@@ -118,6 +133,13 @@ function evaluateGateArtifacts({ root, config, gateDef, phase, result }) {
|
|
|
118
133
|
failures.push(prefixSemanticReason(artifact.path, semanticCheck.reason));
|
|
119
134
|
}
|
|
120
135
|
}
|
|
136
|
+
|
|
137
|
+
// Charter enforcement: verify owning role participated in this phase
|
|
138
|
+
if (artifact.owned_by && !hasRoleParticipationInPhase(state, phase, artifact.owned_by)) {
|
|
139
|
+
failures.push(
|
|
140
|
+
`"${artifact.path}" requires participation from role "${artifact.owned_by}" in phase "${phase}", but no accepted turn from that role was found`,
|
|
141
|
+
);
|
|
142
|
+
}
|
|
121
143
|
}
|
|
122
144
|
|
|
123
145
|
return failures;
|
|
@@ -219,13 +241,14 @@ export function evaluatePhaseExit({ state, config, acceptedTurn, root }) {
|
|
|
219
241
|
|
|
220
242
|
const failures = [];
|
|
221
243
|
|
|
222
|
-
// Predicate: requires_files
|
|
244
|
+
// Predicate: requires_files + ownership
|
|
223
245
|
failures.push(...evaluateGateArtifacts({
|
|
224
246
|
root,
|
|
225
247
|
config,
|
|
226
248
|
gateDef,
|
|
227
249
|
phase: currentPhase,
|
|
228
250
|
result,
|
|
251
|
+
state,
|
|
229
252
|
}));
|
|
230
253
|
|
|
231
254
|
// Predicate: requires_verification_pass
|
|
@@ -341,6 +364,7 @@ export function evaluateRunCompletion({ state, config, acceptedTurn, root }) {
|
|
|
341
364
|
gateDef,
|
|
342
365
|
phase: currentPhase,
|
|
343
366
|
result,
|
|
367
|
+
state,
|
|
344
368
|
}));
|
|
345
369
|
|
|
346
370
|
if (gateDef.requires_verification_pass) {
|
|
@@ -2129,6 +2129,7 @@ function _acceptGovernedTurnLocked(root, config, opts) {
|
|
|
2129
2129
|
turn_id: turnResult.turn_id,
|
|
2130
2130
|
run_id: turnResult.run_id,
|
|
2131
2131
|
role: turnResult.role,
|
|
2132
|
+
phase: state.phase,
|
|
2132
2133
|
runtime_id: turnResult.runtime_id,
|
|
2133
2134
|
status: turnResult.status,
|
|
2134
2135
|
summary: turnResult.summary,
|
|
@@ -2280,12 +2281,13 @@ function _acceptGovernedTurnLocked(root, config, opts) {
|
|
|
2280
2281
|
};
|
|
2281
2282
|
}
|
|
2282
2283
|
} else {
|
|
2284
|
+
const nextHistoryEntries = [...historyEntries, historyEntry];
|
|
2283
2285
|
const postAcceptanceState = {
|
|
2284
2286
|
...state,
|
|
2285
2287
|
active_turns: remainingTurns,
|
|
2286
2288
|
turn_sequence: acceptedSequence,
|
|
2289
|
+
history: nextHistoryEntries,
|
|
2287
2290
|
};
|
|
2288
|
-
const nextHistoryEntries = [...historyEntries, historyEntry];
|
|
2289
2291
|
const completionSource = turnResult.run_completion_request
|
|
2290
2292
|
? turnResult
|
|
2291
2293
|
: findHistoryTurnRequest(nextHistoryEntries, state.queued_run_completion?.requested_by_turn, 'run_completion');
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
2
2
|
import { dirname, join } from 'node:path';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { validateV4Config } from './normalized-config.js';
|
|
4
5
|
|
|
5
6
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
6
7
|
|
|
@@ -11,9 +12,10 @@ export const VALID_GOVERNED_TEMPLATE_IDS = Object.freeze([
|
|
|
11
12
|
'cli-tool',
|
|
12
13
|
'library',
|
|
13
14
|
'web-app',
|
|
15
|
+
'enterprise-app',
|
|
14
16
|
]);
|
|
15
17
|
|
|
16
|
-
const
|
|
18
|
+
const VALID_ROLE_ID_PATTERN = /^[a-z0-9_-]+$/;
|
|
17
19
|
|
|
18
20
|
function validatePlanningArtifacts(artifacts, errors) {
|
|
19
21
|
if (!Array.isArray(artifacts)) {
|
|
@@ -50,8 +52,8 @@ function validatePromptOverrides(promptOverrides, errors) {
|
|
|
50
52
|
}
|
|
51
53
|
|
|
52
54
|
for (const [roleId, content] of Object.entries(promptOverrides)) {
|
|
53
|
-
if (!
|
|
54
|
-
errors.push(`prompt_overrides contains
|
|
55
|
+
if (!VALID_ROLE_ID_PATTERN.test(roleId)) {
|
|
56
|
+
errors.push(`prompt_overrides contains invalid role ID "${roleId}" (must match ${VALID_ROLE_ID_PATTERN})`);
|
|
55
57
|
}
|
|
56
58
|
if (typeof content !== 'string' || !content.trim()) {
|
|
57
59
|
errors.push(`prompt_overrides["${roleId}"] must be a non-empty string`);
|
|
@@ -73,6 +75,47 @@ function validateAcceptanceHints(acceptanceHints, errors) {
|
|
|
73
75
|
}
|
|
74
76
|
}
|
|
75
77
|
|
|
78
|
+
const VALID_SCAFFOLD_BLUEPRINT_KEYS = new Set([
|
|
79
|
+
'roles',
|
|
80
|
+
'runtimes',
|
|
81
|
+
'routing',
|
|
82
|
+
'gates',
|
|
83
|
+
'workflow_kit',
|
|
84
|
+
]);
|
|
85
|
+
|
|
86
|
+
function validateScaffoldBlueprint(scaffoldBlueprint, errors) {
|
|
87
|
+
if (scaffoldBlueprint === undefined) return;
|
|
88
|
+
if (!scaffoldBlueprint || typeof scaffoldBlueprint !== 'object' || Array.isArray(scaffoldBlueprint)) {
|
|
89
|
+
errors.push('scaffold_blueprint must be an object when provided');
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
for (const key of Object.keys(scaffoldBlueprint)) {
|
|
94
|
+
if (!VALID_SCAFFOLD_BLUEPRINT_KEYS.has(key)) {
|
|
95
|
+
errors.push(`scaffold_blueprint contains unknown key "${key}"`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const validation = validateV4Config({
|
|
100
|
+
schema_version: '1.0',
|
|
101
|
+
project: {
|
|
102
|
+
id: 'template-manifest',
|
|
103
|
+
name: 'Template Manifest',
|
|
104
|
+
},
|
|
105
|
+
roles: scaffoldBlueprint.roles,
|
|
106
|
+
runtimes: scaffoldBlueprint.runtimes,
|
|
107
|
+
routing: scaffoldBlueprint.routing,
|
|
108
|
+
gates: scaffoldBlueprint.gates,
|
|
109
|
+
workflow_kit: scaffoldBlueprint.workflow_kit,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
if (!validation.ok) {
|
|
113
|
+
for (const error of validation.errors) {
|
|
114
|
+
errors.push(`scaffold_blueprint ${error}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
76
119
|
const VALID_SPEC_OVERLAY_KEYS = new Set([
|
|
77
120
|
'purpose_guidance',
|
|
78
121
|
'interface_guidance',
|
|
@@ -138,6 +181,7 @@ export function validateGovernedTemplateManifest(manifest, expectedId = null) {
|
|
|
138
181
|
validatePromptOverrides(manifest.prompt_overrides, errors);
|
|
139
182
|
validateAcceptanceHints(manifest.acceptance_hints, errors);
|
|
140
183
|
validateSystemSpecOverlay(manifest.system_spec_overlay, errors);
|
|
184
|
+
validateScaffoldBlueprint(manifest.scaffold_blueprint, errors);
|
|
141
185
|
|
|
142
186
|
return { ok: errors.length === 0, errors };
|
|
143
187
|
}
|
|
@@ -472,7 +472,7 @@ export function validateV4Config(data, projectRoot) {
|
|
|
472
472
|
|
|
473
473
|
// Workflow Kit (optional but validated if present)
|
|
474
474
|
if (data.workflow_kit !== undefined) {
|
|
475
|
-
const wkValidation = validateWorkflowKitConfig(data.workflow_kit, data.routing);
|
|
475
|
+
const wkValidation = validateWorkflowKitConfig(data.workflow_kit, data.routing, data.roles);
|
|
476
476
|
errors.push(...wkValidation.errors);
|
|
477
477
|
}
|
|
478
478
|
|
|
@@ -483,7 +483,7 @@ export function validateV4Config(data, projectRoot) {
|
|
|
483
483
|
* Validate the workflow_kit config section.
|
|
484
484
|
* Returns { ok, errors, warnings }.
|
|
485
485
|
*/
|
|
486
|
-
export function validateWorkflowKitConfig(wk, routing) {
|
|
486
|
+
export function validateWorkflowKitConfig(wk, routing, roles) {
|
|
487
487
|
const errors = [];
|
|
488
488
|
const warnings = [];
|
|
489
489
|
|
|
@@ -574,6 +574,16 @@ export function validateWorkflowKitConfig(wk, routing) {
|
|
|
574
574
|
if (artifact.required !== undefined && typeof artifact.required !== 'boolean') {
|
|
575
575
|
errors.push(`${prefix} required must be a boolean`);
|
|
576
576
|
}
|
|
577
|
+
|
|
578
|
+
if (artifact.owned_by !== undefined && artifact.owned_by !== null) {
|
|
579
|
+
if (typeof artifact.owned_by !== 'string') {
|
|
580
|
+
errors.push(`${prefix} owned_by must be a string`);
|
|
581
|
+
} else if (!/^[a-z0-9_-]+$/.test(artifact.owned_by)) {
|
|
582
|
+
errors.push(`${prefix} owned_by "${artifact.owned_by}" is not a valid role ID (must be lowercase alphanumeric with hyphens/underscores)`);
|
|
583
|
+
} else if (roles && typeof roles === 'object' && !roles[artifact.owned_by]) {
|
|
584
|
+
errors.push(`${prefix} owned_by "${artifact.owned_by}" does not reference a defined role`);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
577
587
|
}
|
|
578
588
|
}
|
|
579
589
|
}
|
|
@@ -791,6 +801,7 @@ export function normalizeWorkflowKit(raw, routingPhases) {
|
|
|
791
801
|
path: a.path,
|
|
792
802
|
semantics: a.semantics || null,
|
|
793
803
|
semantics_config: a.semantics_config || null,
|
|
804
|
+
owned_by: a.owned_by || null,
|
|
794
805
|
required: a.required !== false,
|
|
795
806
|
})),
|
|
796
807
|
};
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "enterprise-app",
|
|
3
|
+
"display_name": "Enterprise App",
|
|
4
|
+
"description": "Governed scaffold for multi-role product delivery with explicit architecture and security-review phases.",
|
|
5
|
+
"version": "1",
|
|
6
|
+
"protocol_compatibility": ["1.0", "1.1"],
|
|
7
|
+
"planning_artifacts": [
|
|
8
|
+
{
|
|
9
|
+
"filename": "integration-boundaries.md",
|
|
10
|
+
"content_template": "# Integration Boundaries — {{project_name}}\n\n## Systems And Owners\n| System | Owner | Dependency type | Contract |\n|--------|-------|-----------------|----------|\n| | | | |\n\n## Cross-Team Dependencies\n- External approvals required:\n- Deployment dependencies:\n- Data-sharing constraints:\n"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"filename": "data-classification.md",
|
|
14
|
+
"content_template": "# Data Classification — {{project_name}}\n\n## Data Types\n| Data type | Sensitivity | Storage boundary | Notes |\n|-----------|-------------|------------------|-------|\n| | | | |\n\n## Compliance Constraints\n- Retention requirements:\n- Encryption expectations:\n- Audit obligations:\n"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"filename": "risk-register.md",
|
|
18
|
+
"content_template": "# Risk Register — {{project_name}}\n\n## Delivery Risks\n| Risk | Likelihood | Impact | Mitigation | Owner |\n|------|------------|--------|------------|-------|\n| | | | | |\n\n## Ship Blockers\n- Blocking risk thresholds:\n- Conditions for mitigated ship:\n"
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"prompt_overrides": {
|
|
22
|
+
"pm": "Drive cross-functional clarity early. Enterprise delivery fails when integration owners, rollout boundaries, or security obligations are left implicit.",
|
|
23
|
+
"architect": "Own the architecture phase. Make interface boundaries, data flow, and trade-offs explicit before dev starts implementation.",
|
|
24
|
+
"dev": "Treat integration safety, data handling, and architecture drift as first-class delivery risks. Do not bypass the architecture or security contracts.",
|
|
25
|
+
"security_reviewer": "Threat-model the shipped change, record findings explicitly, and block progression when risks are unresolved or undocumented.",
|
|
26
|
+
"qa": "Do not sign off until the architecture and security-review artifacts agree with the shipped behavior and verification evidence."
|
|
27
|
+
},
|
|
28
|
+
"acceptance_hints": [
|
|
29
|
+
"Architecture decisions reviewed before implementation started",
|
|
30
|
+
"Security review findings closed or explicitly accepted",
|
|
31
|
+
"Integration boundaries and rollout risks documented before ship request"
|
|
32
|
+
],
|
|
33
|
+
"system_spec_overlay": {
|
|
34
|
+
"purpose_guidance": "Describe the enterprise workflow, the internal or external users it serves, and why this product needs governed multi-role delivery instead of a simple feature implementation.",
|
|
35
|
+
"interface_guidance": "List the product boundaries, key integrations, data flows, and authority handoffs. Reference integration-boundaries.md for the cross-system contract.",
|
|
36
|
+
"behavior_guidance": "Describe the expected system behavior across architecture, implementation, and security-review concerns. Include deployment and rollback assumptions.",
|
|
37
|
+
"error_cases_guidance": "List integration failures, auth or entitlement risks, rollout hazards, and security findings that would block release. Reference risk-register.md for mitigation expectations.",
|
|
38
|
+
"acceptance_tests_guidance": "- [ ] Architecture artifact documents boundaries and trade-offs\n- [ ] Security review captures findings and explicit verdict\n- [ ] Verification covers critical user and integration paths\n- [ ] Rollout or mitigation plan is documented before ship approval"
|
|
39
|
+
},
|
|
40
|
+
"scaffold_blueprint": {
|
|
41
|
+
"roles": {
|
|
42
|
+
"pm": {
|
|
43
|
+
"title": "Product Manager",
|
|
44
|
+
"mandate": "Protect scope clarity, user value, and cross-functional delivery readiness.",
|
|
45
|
+
"write_authority": "review_only",
|
|
46
|
+
"runtime": "manual-pm"
|
|
47
|
+
},
|
|
48
|
+
"architect": {
|
|
49
|
+
"title": "Architect",
|
|
50
|
+
"mandate": "Define the system boundary, integration contracts, and technical trade-offs before implementation commits to a design.",
|
|
51
|
+
"write_authority": "review_only",
|
|
52
|
+
"runtime": "manual-architect"
|
|
53
|
+
},
|
|
54
|
+
"dev": {
|
|
55
|
+
"title": "Developer",
|
|
56
|
+
"mandate": "Implement approved work safely and verify behavior.",
|
|
57
|
+
"write_authority": "authoritative",
|
|
58
|
+
"runtime": "local-dev"
|
|
59
|
+
},
|
|
60
|
+
"security_reviewer": {
|
|
61
|
+
"title": "Security Reviewer",
|
|
62
|
+
"mandate": "Challenge data handling, auth boundaries, and exploit paths before work proceeds to final QA.",
|
|
63
|
+
"write_authority": "review_only",
|
|
64
|
+
"runtime": "manual-security"
|
|
65
|
+
},
|
|
66
|
+
"qa": {
|
|
67
|
+
"title": "QA",
|
|
68
|
+
"mandate": "Challenge correctness, acceptance coverage, and ship readiness.",
|
|
69
|
+
"write_authority": "review_only",
|
|
70
|
+
"runtime": "api-qa"
|
|
71
|
+
},
|
|
72
|
+
"eng_director": {
|
|
73
|
+
"title": "Engineering Director",
|
|
74
|
+
"mandate": "Resolve tactical deadlocks and enforce technical coherence.",
|
|
75
|
+
"write_authority": "review_only",
|
|
76
|
+
"runtime": "manual-director"
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
"runtimes": {
|
|
80
|
+
"manual-pm": { "type": "manual" },
|
|
81
|
+
"local-dev": {
|
|
82
|
+
"type": "local_cli",
|
|
83
|
+
"command": ["claude", "--print", "--dangerously-skip-permissions"],
|
|
84
|
+
"cwd": ".",
|
|
85
|
+
"prompt_transport": "stdin"
|
|
86
|
+
},
|
|
87
|
+
"api-qa": {
|
|
88
|
+
"type": "api_proxy",
|
|
89
|
+
"provider": "anthropic",
|
|
90
|
+
"model": "claude-sonnet-4-6",
|
|
91
|
+
"auth_env": "ANTHROPIC_API_KEY"
|
|
92
|
+
},
|
|
93
|
+
"manual-qa": { "type": "manual" },
|
|
94
|
+
"manual-architect": { "type": "manual" },
|
|
95
|
+
"manual-security": { "type": "manual" },
|
|
96
|
+
"manual-director": { "type": "manual" }
|
|
97
|
+
},
|
|
98
|
+
"routing": {
|
|
99
|
+
"planning": {
|
|
100
|
+
"entry_role": "pm",
|
|
101
|
+
"allowed_next_roles": ["pm", "architect", "eng_director", "human"],
|
|
102
|
+
"exit_gate": "planning_signoff"
|
|
103
|
+
},
|
|
104
|
+
"architecture": {
|
|
105
|
+
"entry_role": "architect",
|
|
106
|
+
"allowed_next_roles": ["architect", "dev", "pm", "eng_director", "human"],
|
|
107
|
+
"exit_gate": "architecture_review"
|
|
108
|
+
},
|
|
109
|
+
"implementation": {
|
|
110
|
+
"entry_role": "dev",
|
|
111
|
+
"allowed_next_roles": ["dev", "security_reviewer", "qa", "architect", "eng_director", "human"],
|
|
112
|
+
"exit_gate": "implementation_complete"
|
|
113
|
+
},
|
|
114
|
+
"security_review": {
|
|
115
|
+
"entry_role": "security_reviewer",
|
|
116
|
+
"allowed_next_roles": ["dev", "qa", "architect", "eng_director", "human"],
|
|
117
|
+
"exit_gate": "security_review_signoff"
|
|
118
|
+
},
|
|
119
|
+
"qa": {
|
|
120
|
+
"entry_role": "qa",
|
|
121
|
+
"allowed_next_roles": ["dev", "qa", "eng_director", "human"],
|
|
122
|
+
"exit_gate": "qa_ship_verdict"
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
"gates": {
|
|
126
|
+
"planning_signoff": {
|
|
127
|
+
"requires_files": [".planning/PM_SIGNOFF.md", ".planning/ROADMAP.md", ".planning/SYSTEM_SPEC.md"],
|
|
128
|
+
"requires_human_approval": true
|
|
129
|
+
},
|
|
130
|
+
"architecture_review": {
|
|
131
|
+
"requires_files": [".planning/ARCHITECTURE.md"]
|
|
132
|
+
},
|
|
133
|
+
"implementation_complete": {
|
|
134
|
+
"requires_files": [".planning/IMPLEMENTATION_NOTES.md"],
|
|
135
|
+
"requires_verification_pass": true
|
|
136
|
+
},
|
|
137
|
+
"security_review_signoff": {
|
|
138
|
+
"requires_files": [".planning/SECURITY_REVIEW.md"]
|
|
139
|
+
},
|
|
140
|
+
"qa_ship_verdict": {
|
|
141
|
+
"requires_files": [".planning/acceptance-matrix.md", ".planning/ship-verdict.md", ".planning/RELEASE_NOTES.md"],
|
|
142
|
+
"requires_human_approval": true
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
"workflow_kit": {
|
|
146
|
+
"phases": {
|
|
147
|
+
"planning": {
|
|
148
|
+
"artifacts": [
|
|
149
|
+
{ "path": ".planning/PM_SIGNOFF.md", "semantics": "pm_signoff", "required": true },
|
|
150
|
+
{ "path": ".planning/SYSTEM_SPEC.md", "semantics": "system_spec", "required": true },
|
|
151
|
+
{ "path": ".planning/ROADMAP.md", "semantics": null, "required": true }
|
|
152
|
+
]
|
|
153
|
+
},
|
|
154
|
+
"architecture": {
|
|
155
|
+
"artifacts": [
|
|
156
|
+
{
|
|
157
|
+
"path": ".planning/ARCHITECTURE.md",
|
|
158
|
+
"semantics": "section_check",
|
|
159
|
+
"owned_by": "architect",
|
|
160
|
+
"semantics_config": {
|
|
161
|
+
"required_sections": ["## Context", "## Proposed Design", "## Trade-offs", "## Risks"]
|
|
162
|
+
},
|
|
163
|
+
"required": true
|
|
164
|
+
}
|
|
165
|
+
]
|
|
166
|
+
},
|
|
167
|
+
"implementation": {
|
|
168
|
+
"artifacts": [
|
|
169
|
+
{ "path": ".planning/IMPLEMENTATION_NOTES.md", "semantics": "implementation_notes", "required": true }
|
|
170
|
+
]
|
|
171
|
+
},
|
|
172
|
+
"security_review": {
|
|
173
|
+
"artifacts": [
|
|
174
|
+
{
|
|
175
|
+
"path": ".planning/SECURITY_REVIEW.md",
|
|
176
|
+
"semantics": "section_check",
|
|
177
|
+
"owned_by": "security_reviewer",
|
|
178
|
+
"semantics_config": {
|
|
179
|
+
"required_sections": ["## Threat Model", "## Findings", "## Verdict"]
|
|
180
|
+
},
|
|
181
|
+
"required": true
|
|
182
|
+
}
|
|
183
|
+
]
|
|
184
|
+
},
|
|
185
|
+
"qa": {
|
|
186
|
+
"artifacts": [
|
|
187
|
+
{ "path": ".planning/acceptance-matrix.md", "semantics": "acceptance_matrix", "required": true },
|
|
188
|
+
{ "path": ".planning/ship-verdict.md", "semantics": "ship_verdict", "required": true },
|
|
189
|
+
{ "path": ".planning/RELEASE_NOTES.md", "semantics": "release_notes", "required": true }
|
|
190
|
+
]
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|