|
6 | 6 | * Reconciles: |
7 | 7 | * - governance/tool-grants.json (meta keys filtered via key.startsWith('_')) |
8 | 8 | * - Each *.agent.md file's frontmatter (model_role, tools) |
9 | | - * - plans/project-context.md executor / review-pipeline / Agent Role Matrix tables |
| 9 | + * - governance/project-context-registry.json executor / review-pipeline / Agent Role Matrix |
10 | 10 | * |
11 | 11 | * Exported helpers are covered by evals/tests/capability-matrix.test.mjs. |
12 | 12 | * |
@@ -59,77 +59,38 @@ export function parseAgentFrontmatter(content) { |
59 | 59 | return { modelRole, tools }; |
60 | 60 | } |
61 | 61 |
|
62 | | -// ── parseProjectContextRoster ───────────────────────────────────────────────── |
| 62 | +// ── loadRosterFromRegistry ────────────────────────────────────────────────────── |
63 | 63 |
|
64 | 64 | /** |
65 | | - * Parse plans/project-context.md to extract executor agents, review-pipeline |
66 | | - * agents, and the Agent Role Matrix table. |
| 65 | + * Load executor/review roster and role matrix metadata from the machine-readable |
| 66 | + * governance registry (governance/project-context-registry.json). |
67 | 67 | * |
68 | | - * Recognised section headers (exact markdown `##` headings): |
69 | | - * - "Phase Executor Agents" |
70 | | - * - "Review Pipeline Agents" |
71 | | - * - "Agent Role Matrix" |
| 68 | + * Maps registry snake_case fields to the camelCase roleMatrix shape used by |
| 69 | + * buildCapabilityMatrix: |
| 70 | + * schema_output → schemaOutput |
| 71 | + * tools_profile → toolsProfile |
| 72 | + * delegation_source → delegationSource |
72 | 73 | * |
73 | | - * Agent names are stored without the `.agent.md` suffix (matching table values). |
74 | | - * |
75 | | - * @param {string} content - Raw file content of project-context.md. |
| 74 | + * @param {string} registryPath - Absolute path to project-context-registry.json. |
76 | 75 | * @returns {{ |
77 | 76 | * executors: string[], |
78 | 77 | * reviewPipeline: string[], |
79 | 78 | * roleMatrix: Map<string, { schemaOutput: string, toolsProfile: string, delegationSource: string }> |
80 | 79 | * }} |
81 | 80 | */ |
82 | | -export function parseProjectContextRoster(content) { |
83 | | - const lines = content.split('\n'); |
84 | | - |
85 | | - const executors = []; |
86 | | - const reviewPipeline = []; |
87 | | - const roleMatrix = new Map(); |
88 | | - |
89 | | - let section = null; |
| 81 | +export function loadRosterFromRegistry(registryPath) { |
| 82 | + const registry = JSON.parse(readFileSync(registryPath, 'utf8')); |
90 | 83 |
|
91 | | - for (const line of lines) { |
92 | | - // Detect targeted section headers first (continue to skip generic ## check) |
93 | | - if (/^##\s+Phase Executor Agents/.test(line)) { |
94 | | - section = 'executors'; |
95 | | - continue; |
96 | | - } |
97 | | - if (/^##\s+Review Pipeline Agents/.test(line)) { |
98 | | - section = 'reviewPipeline'; |
99 | | - continue; |
100 | | - } |
101 | | - if (/^##\s+Agent Role Matrix/.test(line)) { |
102 | | - section = 'roleMatrix'; |
103 | | - continue; |
104 | | - } |
105 | | - // Any other ## heading exits the tracked sections |
106 | | - if (/^##/.test(line)) { |
107 | | - section = null; |
108 | | - continue; |
109 | | - } |
| 84 | + const executors = (registry.phase_executor_agents || []).map((e) => e.agent); |
| 85 | + const reviewPipeline = (registry.review_pipeline_agents || []).map((r) => r.agent); |
110 | 86 |
|
111 | | - if (!section || !line.includes('|')) continue; |
112 | | - |
113 | | - // Skip markdown separator rows (only -, |, :, space) |
114 | | - if (/^\s*\|[\s|:-]+\|\s*$/.test(line)) continue; |
115 | | - |
116 | | - // Split pipe-bordered row; meaningful cells start at index 1 |
117 | | - const cells = line.split('|').map((c) => c.trim()); |
118 | | - if (cells.length < 3) continue; |
119 | | - |
120 | | - const agentName = cells[1]; |
121 | | - if (!agentName || agentName === 'Agent') continue; // skip header row |
122 | | - |
123 | | - if (section === 'executors') { |
124 | | - executors.push(agentName); |
125 | | - } else if (section === 'reviewPipeline') { |
126 | | - reviewPipeline.push(agentName); |
127 | | - } else if (section === 'roleMatrix') { |
128 | | - const schemaOutput = cells[2] ? cells[2].trim() : ''; |
129 | | - const toolsProfile = cells[3] ? cells[3].trim() : ''; |
130 | | - const delegationSource = cells[4] ? cells[4].trim() : ''; |
131 | | - roleMatrix.set(agentName, { schemaOutput, toolsProfile, delegationSource }); |
132 | | - } |
| 87 | + const roleMatrix = new Map(); |
| 88 | + for (const entry of registry.agent_role_matrix || []) { |
| 89 | + roleMatrix.set(entry.agent, { |
| 90 | + schemaOutput: entry.schema_output || '', |
| 91 | + toolsProfile: entry.tools_profile || '', |
| 92 | + delegationSource: entry.delegation_source || '', |
| 93 | + }); |
133 | 94 | } |
134 | 95 |
|
135 | 96 | return { executors, reviewPipeline, roleMatrix }; |
@@ -268,10 +229,10 @@ const isMain = process.argv[1] && resolve(process.argv[1]) === resolve(__filenam |
268 | 229 |
|
269 | 230 | if (isMain) { |
270 | 231 | const grantsPath = join(ROOT, 'governance', 'tool-grants.json'); |
271 | | - const projectContextPath = join(ROOT, 'plans', 'project-context.md'); |
| 232 | + const registryPath = join(ROOT, 'governance', 'project-context-registry.json'); |
272 | 233 |
|
273 | 234 | const grants = JSON.parse(readFileSync(grantsPath, 'utf8')); |
274 | | - const roster = parseProjectContextRoster(readFileSync(projectContextPath, 'utf8')); |
| 235 | + const roster = loadRosterFromRegistry(registryPath); |
275 | 236 |
|
276 | 237 | const agents = new Map(); |
277 | 238 | for (const agentFile of Object.keys(grants)) { |
|
0 commit comments