@forwardimpact/pathway 0.1.0 → 0.3.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/app/commands/agent.js +119 -31
- package/app/commands/command-factory.js +3 -3
- package/app/commands/interview.js +14 -7
- package/app/commands/job.js +52 -33
- package/app/commands/progress.js +14 -7
- package/app/commands/serve.js +5 -0
- package/app/commands/stage.js +0 -10
- package/app/commands/track.js +5 -8
- package/app/components/builder.js +117 -30
- package/app/css/components/surfaces.css +16 -0
- package/app/formatters/agent/profile.js +30 -115
- package/app/formatters/agent/skill.js +23 -44
- package/app/formatters/behaviour/dom.js +3 -0
- package/app/formatters/behaviour/microdata.js +106 -0
- package/app/formatters/discipline/dom.js +28 -1
- package/app/formatters/discipline/microdata.js +117 -0
- package/app/formatters/discipline/shared.js +49 -8
- package/app/formatters/driver/dom.js +3 -0
- package/app/formatters/driver/microdata.js +91 -0
- package/app/formatters/grade/dom.js +5 -4
- package/app/formatters/grade/microdata.js +151 -0
- package/app/formatters/index.js +32 -1
- package/app/formatters/interview/shared.js +13 -8
- package/app/formatters/job/description.js +70 -81
- package/app/formatters/job/dom.js +40 -113
- package/app/formatters/job/markdown.js +17 -13
- package/app/formatters/json-ld.js +242 -0
- package/app/formatters/microdata-shared.js +184 -0
- package/app/formatters/progress/shared.js +14 -11
- package/app/formatters/shared.js +7 -2
- package/app/formatters/skill/dom.js +3 -0
- package/app/formatters/skill/microdata.js +151 -0
- package/app/formatters/stage/dom.js +3 -18
- package/app/formatters/stage/microdata.js +110 -0
- package/app/formatters/stage/shared.js +0 -27
- package/app/formatters/track/dom.js +5 -30
- package/app/formatters/track/markdown.js +2 -25
- package/app/formatters/track/microdata.js +111 -0
- package/app/formatters/track/shared.js +6 -58
- package/app/handout-main.js +26 -12
- package/app/handout.html +7 -0
- package/app/index.html +11 -0
- package/app/lib/card-mappers.js +17 -12
- package/app/lib/form-controls.js +64 -1
- package/app/lib/job-cache.js +12 -9
- package/app/lib/render.js +8 -1
- package/app/lib/template-loader.js +75 -0
- package/app/lib/yaml-loader.js +25 -8
- package/app/main.js +8 -4
- package/app/model/agent.js +158 -130
- package/app/model/checklist.js +57 -91
- package/app/model/derivation.js +135 -68
- package/app/model/index-generator.js +1 -7
- package/app/model/job.js +19 -13
- package/app/model/levels.js +20 -12
- package/app/model/loader.js +41 -17
- package/app/model/matching.js +33 -3
- package/app/model/profile.js +38 -45
- package/app/model/schema-validation.js +438 -0
- package/app/model/validation.js +747 -68
- package/app/pages/agent-builder.js +125 -28
- package/app/pages/assessment-results.js +10 -4
- package/app/pages/discipline.js +36 -6
- package/app/pages/driver.js +9 -47
- package/app/pages/interview-builder.js +3 -1
- package/app/pages/interview.js +15 -4
- package/app/pages/job-builder.js +4 -1
- package/app/pages/job.js +43 -8
- package/app/pages/landing.js +10 -10
- package/app/pages/progress-builder.js +3 -1
- package/app/pages/progress.js +78 -26
- package/app/pages/self-assessment.js +3 -3
- package/app/pages/stage.js +3 -126
- package/app/slide-main.js +45 -17
- package/app/slides/index.js +3 -1
- package/app/slides/overview.js +40 -4
- package/app/slides/progress.js +4 -2
- package/app/slides.html +7 -0
- package/bin/pathway.js +28 -75
- package/examples/agents/.claude/skills/architecture-design/SKILL.md +58 -16
- package/examples/agents/.claude/skills/cloud-platforms/SKILL.md +59 -18
- package/examples/agents/.claude/skills/code-quality-review/SKILL.md +58 -17
- package/examples/agents/.claude/skills/devops-cicd/SKILL.md +64 -18
- package/examples/agents/.claude/skills/full-stack-development/SKILL.md +59 -15
- package/examples/agents/.claude/skills/sre-practices/SKILL.md +64 -18
- package/examples/agents/.claude/skills/technical-debt-management/SKILL.md +58 -17
- package/examples/agents/.github/agents/se-platform-code.agent.md +39 -88
- package/examples/agents/.github/agents/se-platform-plan.agent.md +41 -88
- package/examples/agents/.github/agents/se-platform-review.agent.md +38 -15
- package/examples/agents/.vscode/settings.json +1 -1
- package/examples/behaviours/outcome_ownership.yaml +1 -2
- package/examples/behaviours/polymathic_knowledge.yaml +1 -2
- package/examples/behaviours/precise_communication.yaml +1 -2
- package/examples/behaviours/relentless_curiosity.yaml +1 -2
- package/examples/behaviours/systems_thinking.yaml +1 -2
- package/examples/capabilities/business.yaml +80 -142
- package/examples/capabilities/delivery.yaml +155 -219
- package/examples/capabilities/people.yaml +2 -34
- package/examples/capabilities/reliability.yaml +161 -80
- package/examples/capabilities/scale.yaml +234 -252
- package/examples/copilot-setup-steps.yaml +25 -0
- package/examples/devcontainer.yaml +21 -0
- package/examples/disciplines/_index.yaml +1 -0
- package/examples/disciplines/data_engineering.yaml +14 -12
- package/examples/disciplines/engineering_management.yaml +63 -0
- package/examples/disciplines/software_engineering.yaml +14 -12
- package/examples/drivers.yaml +1 -4
- package/examples/framework.yaml +1 -2
- package/examples/grades.yaml +14 -15
- package/examples/questions/behaviours/outcome_ownership.yaml +1 -2
- package/examples/questions/behaviours/polymathic_knowledge.yaml +1 -2
- package/examples/questions/behaviours/precise_communication.yaml +1 -2
- package/examples/questions/behaviours/relentless_curiosity.yaml +1 -2
- package/examples/questions/behaviours/systems_thinking.yaml +1 -2
- package/examples/questions/skills/architecture_design.yaml +1 -2
- package/examples/questions/skills/cloud_platforms.yaml +1 -2
- package/examples/questions/skills/code_quality.yaml +1 -2
- package/examples/questions/skills/data_modeling.yaml +1 -2
- package/examples/questions/skills/devops.yaml +1 -2
- package/examples/questions/skills/full_stack_development.yaml +1 -2
- package/examples/questions/skills/sre_practices.yaml +1 -2
- package/examples/questions/skills/stakeholder_management.yaml +1 -2
- package/examples/questions/skills/team_collaboration.yaml +1 -2
- package/examples/questions/skills/technical_writing.yaml +1 -2
- package/examples/self-assessments.yaml +1 -3
- package/examples/stages.yaml +101 -46
- package/examples/tracks/_index.yaml +0 -1
- package/examples/tracks/platform.yaml +8 -13
- package/examples/tracks/sre.yaml +8 -18
- package/examples/vscode-settings.yaml +2 -7
- package/package.json +9 -3
- package/templates/agent.template.md +65 -0
- package/templates/job.template.md +47 -0
- package/templates/skill.template.md +28 -0
- package/examples/agents/.claude/skills/data-modeling/SKILL.md +0 -99
- package/examples/agents/.claude/skills/developer-experience/SKILL.md +0 -99
- package/examples/agents/.claude/skills/knowledge-management/SKILL.md +0 -100
- package/examples/agents/.claude/skills/pattern-generalization/SKILL.md +0 -102
- package/examples/agents/.claude/skills/technical-writing/SKILL.md +0 -129
- package/examples/tracks/manager.yaml +0 -53
package/app/model/validation.js
CHANGED
|
@@ -56,9 +56,10 @@ function createWarning(type, message, path) {
|
|
|
56
56
|
* Validate that a skill has required properties
|
|
57
57
|
* @param {import('./levels.js').Skill} skill - Skill to validate
|
|
58
58
|
* @param {number} index - Index in the skills array
|
|
59
|
+
* @param {string[]} [requiredStageIds] - Stage IDs that must be present in agent skills
|
|
59
60
|
* @returns {{errors: Array, warnings: Array}}
|
|
60
61
|
*/
|
|
61
|
-
function validateSkill(skill, index) {
|
|
62
|
+
function validateSkill(skill, index, requiredStageIds = []) {
|
|
62
63
|
const errors = [];
|
|
63
64
|
const warnings = [];
|
|
64
65
|
const path = `skills[${index}]`;
|
|
@@ -108,6 +109,192 @@ function validateSkill(skill, index) {
|
|
|
108
109
|
);
|
|
109
110
|
}
|
|
110
111
|
|
|
112
|
+
// Validate agent section if present
|
|
113
|
+
if (skill.agent) {
|
|
114
|
+
const agentPath = `${path}.agent`;
|
|
115
|
+
if (!skill.agent.name) {
|
|
116
|
+
errors.push(
|
|
117
|
+
createError(
|
|
118
|
+
"MISSING_REQUIRED",
|
|
119
|
+
"Skill agent section missing name",
|
|
120
|
+
`${agentPath}.name`,
|
|
121
|
+
),
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
if (!skill.agent.description) {
|
|
125
|
+
errors.push(
|
|
126
|
+
createError(
|
|
127
|
+
"MISSING_REQUIRED",
|
|
128
|
+
"Skill agent section missing description",
|
|
129
|
+
`${agentPath}.description`,
|
|
130
|
+
),
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Validate stages (required for agent skills)
|
|
135
|
+
if (!skill.agent.stages) {
|
|
136
|
+
errors.push(
|
|
137
|
+
createError(
|
|
138
|
+
"MISSING_REQUIRED",
|
|
139
|
+
"Skill agent section missing stages",
|
|
140
|
+
`${agentPath}.stages`,
|
|
141
|
+
),
|
|
142
|
+
);
|
|
143
|
+
} else if (typeof skill.agent.stages !== "object") {
|
|
144
|
+
errors.push(
|
|
145
|
+
createError(
|
|
146
|
+
"INVALID_VALUE",
|
|
147
|
+
"Skill agent stages must be an object",
|
|
148
|
+
`${agentPath}.stages`,
|
|
149
|
+
skill.agent.stages,
|
|
150
|
+
),
|
|
151
|
+
);
|
|
152
|
+
} else {
|
|
153
|
+
// Validate each stage
|
|
154
|
+
const validStageIds = Object.values(Stage);
|
|
155
|
+
for (const [stageId, stageData] of Object.entries(skill.agent.stages)) {
|
|
156
|
+
if (!validStageIds.includes(stageId)) {
|
|
157
|
+
errors.push(
|
|
158
|
+
createError(
|
|
159
|
+
"INVALID_VALUE",
|
|
160
|
+
`Invalid stage ID: ${stageId}. Must be one of: ${validStageIds.join(", ")}`,
|
|
161
|
+
`${agentPath}.stages.${stageId}`,
|
|
162
|
+
stageId,
|
|
163
|
+
),
|
|
164
|
+
);
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
const stagePath = `${agentPath}.stages.${stageId}`;
|
|
168
|
+
// focus is required
|
|
169
|
+
if (!stageData.focus) {
|
|
170
|
+
errors.push(
|
|
171
|
+
createError(
|
|
172
|
+
"MISSING_REQUIRED",
|
|
173
|
+
`Stage ${stageId} missing focus`,
|
|
174
|
+
`${stagePath}.focus`,
|
|
175
|
+
),
|
|
176
|
+
);
|
|
177
|
+
} else if (typeof stageData.focus !== "string") {
|
|
178
|
+
errors.push(
|
|
179
|
+
createError(
|
|
180
|
+
"INVALID_VALUE",
|
|
181
|
+
`Stage ${stageId} focus must be a string`,
|
|
182
|
+
`${stagePath}.focus`,
|
|
183
|
+
stageData.focus,
|
|
184
|
+
),
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
// activities is required and must be an array
|
|
188
|
+
if (!stageData.activities) {
|
|
189
|
+
errors.push(
|
|
190
|
+
createError(
|
|
191
|
+
"MISSING_REQUIRED",
|
|
192
|
+
`Stage ${stageId} missing activities`,
|
|
193
|
+
`${stagePath}.activities`,
|
|
194
|
+
),
|
|
195
|
+
);
|
|
196
|
+
} else if (!Array.isArray(stageData.activities)) {
|
|
197
|
+
errors.push(
|
|
198
|
+
createError(
|
|
199
|
+
"INVALID_VALUE",
|
|
200
|
+
`Stage ${stageId} activities must be an array`,
|
|
201
|
+
`${stagePath}.activities`,
|
|
202
|
+
stageData.activities,
|
|
203
|
+
),
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
// ready is required and must be an array (these become checklist items)
|
|
207
|
+
if (!stageData.ready) {
|
|
208
|
+
errors.push(
|
|
209
|
+
createError(
|
|
210
|
+
"MISSING_REQUIRED",
|
|
211
|
+
`Stage ${stageId} missing ready criteria`,
|
|
212
|
+
`${stagePath}.ready`,
|
|
213
|
+
),
|
|
214
|
+
);
|
|
215
|
+
} else if (!Array.isArray(stageData.ready)) {
|
|
216
|
+
errors.push(
|
|
217
|
+
createError(
|
|
218
|
+
"INVALID_VALUE",
|
|
219
|
+
`Stage ${stageId} ready must be an array`,
|
|
220
|
+
`${stagePath}.ready`,
|
|
221
|
+
stageData.ready,
|
|
222
|
+
),
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Check that all required stages are present
|
|
228
|
+
if (requiredStageIds.length > 0) {
|
|
229
|
+
const presentStageIds = Object.keys(skill.agent.stages);
|
|
230
|
+
for (const requiredStageId of requiredStageIds) {
|
|
231
|
+
if (!presentStageIds.includes(requiredStageId)) {
|
|
232
|
+
errors.push(
|
|
233
|
+
createError(
|
|
234
|
+
"MISSING_REQUIRED",
|
|
235
|
+
`Skill agent missing required stage: ${requiredStageId}`,
|
|
236
|
+
`${agentPath}.stages.${requiredStageId}`,
|
|
237
|
+
),
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// reference is optional but should be a string if present
|
|
245
|
+
if (
|
|
246
|
+
skill.agent.reference !== undefined &&
|
|
247
|
+
typeof skill.agent.reference !== "string"
|
|
248
|
+
) {
|
|
249
|
+
errors.push(
|
|
250
|
+
createError(
|
|
251
|
+
"INVALID_VALUE",
|
|
252
|
+
"Skill agent reference must be a string",
|
|
253
|
+
`${agentPath}.reference`,
|
|
254
|
+
skill.agent.reference,
|
|
255
|
+
),
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Error if old fields are still present
|
|
260
|
+
if (skill.agent.body !== undefined) {
|
|
261
|
+
errors.push(
|
|
262
|
+
createError(
|
|
263
|
+
"INVALID_FIELD",
|
|
264
|
+
"Skill agent 'body' field is not supported. Use stages instead.",
|
|
265
|
+
`${agentPath}.body`,
|
|
266
|
+
),
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
if (skill.agent.applicability !== undefined) {
|
|
270
|
+
errors.push(
|
|
271
|
+
createError(
|
|
272
|
+
"INVALID_FIELD",
|
|
273
|
+
"Skill agent 'applicability' field is not supported. Use stages instead.",
|
|
274
|
+
`${agentPath}.applicability`,
|
|
275
|
+
),
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
if (skill.agent.guidance !== undefined) {
|
|
279
|
+
errors.push(
|
|
280
|
+
createError(
|
|
281
|
+
"INVALID_FIELD",
|
|
282
|
+
"Skill agent 'guidance' field is not supported. Use stages instead.",
|
|
283
|
+
`${agentPath}.guidance`,
|
|
284
|
+
),
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
if (skill.agent.verificationCriteria !== undefined) {
|
|
288
|
+
errors.push(
|
|
289
|
+
createError(
|
|
290
|
+
"INVALID_FIELD",
|
|
291
|
+
"Skill agent 'verificationCriteria' field is not supported. Use stages.{stage}.ready instead.",
|
|
292
|
+
`${agentPath}.verificationCriteria`,
|
|
293
|
+
),
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
111
298
|
return { errors, warnings };
|
|
112
299
|
}
|
|
113
300
|
|
|
@@ -147,6 +334,51 @@ function validateBehaviour(behaviour, index) {
|
|
|
147
334
|
);
|
|
148
335
|
}
|
|
149
336
|
|
|
337
|
+
// Validate agent section if present
|
|
338
|
+
if (behaviour.agent) {
|
|
339
|
+
const agentPath = `${path}.agent`;
|
|
340
|
+
|
|
341
|
+
// title is required for agent behaviours
|
|
342
|
+
if (!behaviour.agent.title) {
|
|
343
|
+
errors.push(
|
|
344
|
+
createError(
|
|
345
|
+
"MISSING_REQUIRED",
|
|
346
|
+
"Behaviour agent section missing title",
|
|
347
|
+
`${agentPath}.title`,
|
|
348
|
+
),
|
|
349
|
+
);
|
|
350
|
+
} else if (typeof behaviour.agent.title !== "string") {
|
|
351
|
+
errors.push(
|
|
352
|
+
createError(
|
|
353
|
+
"INVALID_VALUE",
|
|
354
|
+
"Behaviour agent title must be a string",
|
|
355
|
+
`${agentPath}.title`,
|
|
356
|
+
behaviour.agent.title,
|
|
357
|
+
),
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// workingStyle is required for agent behaviours
|
|
362
|
+
if (!behaviour.agent.workingStyle) {
|
|
363
|
+
errors.push(
|
|
364
|
+
createError(
|
|
365
|
+
"MISSING_REQUIRED",
|
|
366
|
+
"Behaviour agent section missing workingStyle",
|
|
367
|
+
`${agentPath}.workingStyle`,
|
|
368
|
+
),
|
|
369
|
+
);
|
|
370
|
+
} else if (typeof behaviour.agent.workingStyle !== "string") {
|
|
371
|
+
errors.push(
|
|
372
|
+
createError(
|
|
373
|
+
"INVALID_VALUE",
|
|
374
|
+
"Behaviour agent workingStyle must be a string",
|
|
375
|
+
`${agentPath}.workingStyle`,
|
|
376
|
+
behaviour.agent.workingStyle,
|
|
377
|
+
),
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
150
382
|
return { errors, warnings };
|
|
151
383
|
}
|
|
152
384
|
|
|
@@ -222,9 +454,18 @@ function validateDriver(driver, index, skillIds, behaviourIds) {
|
|
|
222
454
|
* @param {number} index - Index in the disciplines array
|
|
223
455
|
* @param {Set<string>} skillIds - Set of valid skill IDs
|
|
224
456
|
* @param {Set<string>} behaviourIds - Set of valid behaviour IDs
|
|
457
|
+
* @param {Set<string>} trackIds - Set of valid track IDs
|
|
458
|
+
* @param {Set<string>} gradeIds - Set of valid grade IDs
|
|
225
459
|
* @returns {{errors: Array, warnings: Array}}
|
|
226
460
|
*/
|
|
227
|
-
function validateDiscipline(
|
|
461
|
+
function validateDiscipline(
|
|
462
|
+
discipline,
|
|
463
|
+
index,
|
|
464
|
+
skillIds,
|
|
465
|
+
behaviourIds,
|
|
466
|
+
trackIds,
|
|
467
|
+
gradeIds,
|
|
468
|
+
) {
|
|
228
469
|
const errors = [];
|
|
229
470
|
const warnings = [];
|
|
230
471
|
const path = `disciplines[${index}]`;
|
|
@@ -249,6 +490,77 @@ function validateDiscipline(discipline, index, skillIds, behaviourIds) {
|
|
|
249
490
|
);
|
|
250
491
|
}
|
|
251
492
|
|
|
493
|
+
// Validate validTracks (REQUIRED - must be an array)
|
|
494
|
+
// - null in array = allow trackless (generalist)
|
|
495
|
+
// - string values = specific track IDs
|
|
496
|
+
// - empty array = no valid combinations (discipline cannot be used)
|
|
497
|
+
if (!Array.isArray(discipline.validTracks)) {
|
|
498
|
+
errors.push(
|
|
499
|
+
createError(
|
|
500
|
+
"MISSING_REQUIRED",
|
|
501
|
+
`Discipline "${discipline.id}" missing required validTracks array`,
|
|
502
|
+
`${path}.validTracks`,
|
|
503
|
+
),
|
|
504
|
+
);
|
|
505
|
+
} else {
|
|
506
|
+
discipline.validTracks.forEach((trackId, i) => {
|
|
507
|
+
// null means "allow trackless" - skip validation
|
|
508
|
+
if (trackId === null) return;
|
|
509
|
+
if (!trackIds.has(trackId)) {
|
|
510
|
+
errors.push(
|
|
511
|
+
createError(
|
|
512
|
+
"INVALID_REFERENCE",
|
|
513
|
+
`Discipline "${discipline.id}" references non-existent track: ${trackId}`,
|
|
514
|
+
`${path}.validTracks[${i}]`,
|
|
515
|
+
trackId,
|
|
516
|
+
),
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// Validate minGrade if specified
|
|
523
|
+
if (discipline.minGrade) {
|
|
524
|
+
if (!gradeIds.has(discipline.minGrade)) {
|
|
525
|
+
errors.push(
|
|
526
|
+
createError(
|
|
527
|
+
"INVALID_REFERENCE",
|
|
528
|
+
`Discipline "${discipline.id}" references non-existent grade: ${discipline.minGrade}`,
|
|
529
|
+
`${path}.minGrade`,
|
|
530
|
+
discipline.minGrade,
|
|
531
|
+
),
|
|
532
|
+
);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// Validate isManagement/isProfessional booleans (optional)
|
|
537
|
+
if (
|
|
538
|
+
discipline.isManagement !== undefined &&
|
|
539
|
+
typeof discipline.isManagement !== "boolean"
|
|
540
|
+
) {
|
|
541
|
+
errors.push(
|
|
542
|
+
createError(
|
|
543
|
+
"INVALID_VALUE",
|
|
544
|
+
`Discipline "${discipline.id}" has invalid isManagement value: ${discipline.isManagement} (must be boolean)`,
|
|
545
|
+
`${path}.isManagement`,
|
|
546
|
+
discipline.isManagement,
|
|
547
|
+
),
|
|
548
|
+
);
|
|
549
|
+
}
|
|
550
|
+
if (
|
|
551
|
+
discipline.isProfessional !== undefined &&
|
|
552
|
+
typeof discipline.isProfessional !== "boolean"
|
|
553
|
+
) {
|
|
554
|
+
errors.push(
|
|
555
|
+
createError(
|
|
556
|
+
"INVALID_VALUE",
|
|
557
|
+
`Discipline "${discipline.id}" has invalid isProfessional value: ${discipline.isProfessional} (must be boolean)`,
|
|
558
|
+
`${path}.isProfessional`,
|
|
559
|
+
discipline.isProfessional,
|
|
560
|
+
),
|
|
561
|
+
);
|
|
562
|
+
}
|
|
563
|
+
|
|
252
564
|
// Validate core skills
|
|
253
565
|
if (!discipline.coreSkills || discipline.coreSkills.length === 0) {
|
|
254
566
|
errors.push(
|
|
@@ -333,6 +645,126 @@ function validateDiscipline(discipline, index, skillIds, behaviourIds) {
|
|
|
333
645
|
);
|
|
334
646
|
}
|
|
335
647
|
|
|
648
|
+
// Validate agent section if present
|
|
649
|
+
if (discipline.agent) {
|
|
650
|
+
const agentPath = `${path}.agent`;
|
|
651
|
+
|
|
652
|
+
// Required: identity
|
|
653
|
+
if (!discipline.agent.identity) {
|
|
654
|
+
errors.push(
|
|
655
|
+
createError(
|
|
656
|
+
"MISSING_REQUIRED",
|
|
657
|
+
"Discipline agent section missing identity",
|
|
658
|
+
`${agentPath}.identity`,
|
|
659
|
+
),
|
|
660
|
+
);
|
|
661
|
+
} else if (typeof discipline.agent.identity !== "string") {
|
|
662
|
+
errors.push(
|
|
663
|
+
createError(
|
|
664
|
+
"INVALID_VALUE",
|
|
665
|
+
"Discipline agent identity must be a string",
|
|
666
|
+
`${agentPath}.identity`,
|
|
667
|
+
discipline.agent.identity,
|
|
668
|
+
),
|
|
669
|
+
);
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// Optional: priority (string)
|
|
673
|
+
if (
|
|
674
|
+
discipline.agent.priority !== undefined &&
|
|
675
|
+
typeof discipline.agent.priority !== "string"
|
|
676
|
+
) {
|
|
677
|
+
errors.push(
|
|
678
|
+
createError(
|
|
679
|
+
"INVALID_VALUE",
|
|
680
|
+
"Discipline agent priority must be a string",
|
|
681
|
+
`${agentPath}.priority`,
|
|
682
|
+
discipline.agent.priority,
|
|
683
|
+
),
|
|
684
|
+
);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// Optional: beforeMakingChanges (array of strings)
|
|
688
|
+
if (discipline.agent.beforeMakingChanges !== undefined) {
|
|
689
|
+
if (!Array.isArray(discipline.agent.beforeMakingChanges)) {
|
|
690
|
+
errors.push(
|
|
691
|
+
createError(
|
|
692
|
+
"INVALID_VALUE",
|
|
693
|
+
"Discipline agent beforeMakingChanges must be an array",
|
|
694
|
+
`${agentPath}.beforeMakingChanges`,
|
|
695
|
+
discipline.agent.beforeMakingChanges,
|
|
696
|
+
),
|
|
697
|
+
);
|
|
698
|
+
} else {
|
|
699
|
+
discipline.agent.beforeMakingChanges.forEach((item, i) => {
|
|
700
|
+
if (typeof item !== "string") {
|
|
701
|
+
errors.push(
|
|
702
|
+
createError(
|
|
703
|
+
"INVALID_VALUE",
|
|
704
|
+
"Discipline agent beforeMakingChanges items must be strings",
|
|
705
|
+
`${agentPath}.beforeMakingChanges[${i}]`,
|
|
706
|
+
item,
|
|
707
|
+
),
|
|
708
|
+
);
|
|
709
|
+
}
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
// Optional: delegation (string)
|
|
715
|
+
if (
|
|
716
|
+
discipline.agent.delegation !== undefined &&
|
|
717
|
+
typeof discipline.agent.delegation !== "string"
|
|
718
|
+
) {
|
|
719
|
+
errors.push(
|
|
720
|
+
createError(
|
|
721
|
+
"INVALID_VALUE",
|
|
722
|
+
"Discipline agent delegation must be a string",
|
|
723
|
+
`${agentPath}.delegation`,
|
|
724
|
+
discipline.agent.delegation,
|
|
725
|
+
),
|
|
726
|
+
);
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
// Optional: constraints (array of strings)
|
|
730
|
+
if (discipline.agent.constraints !== undefined) {
|
|
731
|
+
if (!Array.isArray(discipline.agent.constraints)) {
|
|
732
|
+
errors.push(
|
|
733
|
+
createError(
|
|
734
|
+
"INVALID_VALUE",
|
|
735
|
+
"Discipline agent constraints must be an array",
|
|
736
|
+
`${agentPath}.constraints`,
|
|
737
|
+
discipline.agent.constraints,
|
|
738
|
+
),
|
|
739
|
+
);
|
|
740
|
+
} else {
|
|
741
|
+
discipline.agent.constraints.forEach((item, i) => {
|
|
742
|
+
if (typeof item !== "string") {
|
|
743
|
+
errors.push(
|
|
744
|
+
createError(
|
|
745
|
+
"INVALID_VALUE",
|
|
746
|
+
"Discipline agent constraints items must be strings",
|
|
747
|
+
`${agentPath}.constraints[${i}]`,
|
|
748
|
+
item,
|
|
749
|
+
),
|
|
750
|
+
);
|
|
751
|
+
}
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
// Error if old 'coreInstructions' field is still present
|
|
757
|
+
if (discipline.agent.coreInstructions !== undefined) {
|
|
758
|
+
errors.push(
|
|
759
|
+
createError(
|
|
760
|
+
"INVALID_FIELD",
|
|
761
|
+
"Discipline agent 'coreInstructions' field is not supported. Use identity, priority, beforeMakingChanges, and delegation instead.",
|
|
762
|
+
`${agentPath}.coreInstructions`,
|
|
763
|
+
),
|
|
764
|
+
);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
336
768
|
return { errors, warnings };
|
|
337
769
|
}
|
|
338
770
|
|
|
@@ -357,7 +789,7 @@ function getAllDisciplineSkillIds(disciplines) {
|
|
|
357
789
|
* @param {number} index - Index in the tracks array
|
|
358
790
|
* @param {Set<string>} disciplineSkillIds - Set of skill IDs used in any discipline
|
|
359
791
|
* @param {Set<string>} behaviourIds - Set of valid behaviour IDs
|
|
360
|
-
* @param {Set<string>}
|
|
792
|
+
* @param {Set<string>} gradeIds - Set of valid grade IDs
|
|
361
793
|
* @returns {{errors: Array, warnings: Array}}
|
|
362
794
|
*/
|
|
363
795
|
function validateTrack(
|
|
@@ -365,7 +797,6 @@ function validateTrack(
|
|
|
365
797
|
index,
|
|
366
798
|
disciplineSkillIds,
|
|
367
799
|
behaviourIds,
|
|
368
|
-
disciplineIds,
|
|
369
800
|
gradeIds,
|
|
370
801
|
) {
|
|
371
802
|
const errors = [];
|
|
@@ -379,34 +810,6 @@ function validateTrack(
|
|
|
379
810
|
);
|
|
380
811
|
}
|
|
381
812
|
|
|
382
|
-
// Validate isProfessional/isManagement booleans (optional, default to isProfessional: true)
|
|
383
|
-
if (
|
|
384
|
-
track.isProfessional !== undefined &&
|
|
385
|
-
typeof track.isProfessional !== "boolean"
|
|
386
|
-
) {
|
|
387
|
-
errors.push(
|
|
388
|
-
createError(
|
|
389
|
-
"INVALID_VALUE",
|
|
390
|
-
`Track "${track.id}" has invalid isProfessional value: ${track.isProfessional} (must be boolean)`,
|
|
391
|
-
`${path}.isProfessional`,
|
|
392
|
-
track.isProfessional,
|
|
393
|
-
),
|
|
394
|
-
);
|
|
395
|
-
}
|
|
396
|
-
if (
|
|
397
|
-
track.isManagement !== undefined &&
|
|
398
|
-
typeof track.isManagement !== "boolean"
|
|
399
|
-
) {
|
|
400
|
-
errors.push(
|
|
401
|
-
createError(
|
|
402
|
-
"INVALID_VALUE",
|
|
403
|
-
`Track "${track.id}" has invalid isManagement value: ${track.isManagement} (must be boolean)`,
|
|
404
|
-
`${path}.isManagement`,
|
|
405
|
-
track.isManagement,
|
|
406
|
-
),
|
|
407
|
-
);
|
|
408
|
-
}
|
|
409
|
-
|
|
410
813
|
// Validate skill modifiers - must be capabilities only (not individual skill IDs)
|
|
411
814
|
if (track.skillModifiers) {
|
|
412
815
|
Object.entries(track.skillModifiers).forEach(([key, modifier]) => {
|
|
@@ -462,22 +865,6 @@ function validateTrack(
|
|
|
462
865
|
);
|
|
463
866
|
}
|
|
464
867
|
|
|
465
|
-
// Validate validDisciplines if specified
|
|
466
|
-
if (track.validDisciplines) {
|
|
467
|
-
track.validDisciplines.forEach((disciplineId, i) => {
|
|
468
|
-
if (!disciplineIds.has(disciplineId)) {
|
|
469
|
-
errors.push(
|
|
470
|
-
createError(
|
|
471
|
-
"INVALID_REFERENCE",
|
|
472
|
-
`Track "${track.id}" references non-existent discipline: ${disciplineId}`,
|
|
473
|
-
`${path}.validDisciplines[${i}]`,
|
|
474
|
-
disciplineId,
|
|
475
|
-
),
|
|
476
|
-
);
|
|
477
|
-
}
|
|
478
|
-
});
|
|
479
|
-
}
|
|
480
|
-
|
|
481
868
|
// Validate minGrade if specified
|
|
482
869
|
if (track.minGrade) {
|
|
483
870
|
if (!gradeIds.has(track.minGrade)) {
|
|
@@ -537,6 +924,106 @@ function validateTrack(
|
|
|
537
924
|
}
|
|
538
925
|
}
|
|
539
926
|
|
|
927
|
+
// Validate agent section if present
|
|
928
|
+
if (track.agent) {
|
|
929
|
+
const agentPath = `${path}.agent`;
|
|
930
|
+
|
|
931
|
+
// Optional: identity (string) - if provided, overrides discipline identity
|
|
932
|
+
if (
|
|
933
|
+
track.agent.identity !== undefined &&
|
|
934
|
+
typeof track.agent.identity !== "string"
|
|
935
|
+
) {
|
|
936
|
+
errors.push(
|
|
937
|
+
createError(
|
|
938
|
+
"INVALID_VALUE",
|
|
939
|
+
"Track agent identity must be a string",
|
|
940
|
+
`${agentPath}.identity`,
|
|
941
|
+
track.agent.identity,
|
|
942
|
+
),
|
|
943
|
+
);
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
// Optional: priority (string)
|
|
947
|
+
if (
|
|
948
|
+
track.agent.priority !== undefined &&
|
|
949
|
+
typeof track.agent.priority !== "string"
|
|
950
|
+
) {
|
|
951
|
+
errors.push(
|
|
952
|
+
createError(
|
|
953
|
+
"INVALID_VALUE",
|
|
954
|
+
"Track agent priority must be a string",
|
|
955
|
+
`${agentPath}.priority`,
|
|
956
|
+
track.agent.priority,
|
|
957
|
+
),
|
|
958
|
+
);
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
// Optional: beforeMakingChanges (array of strings)
|
|
962
|
+
if (track.agent.beforeMakingChanges !== undefined) {
|
|
963
|
+
if (!Array.isArray(track.agent.beforeMakingChanges)) {
|
|
964
|
+
errors.push(
|
|
965
|
+
createError(
|
|
966
|
+
"INVALID_VALUE",
|
|
967
|
+
"Track agent beforeMakingChanges must be an array",
|
|
968
|
+
`${agentPath}.beforeMakingChanges`,
|
|
969
|
+
track.agent.beforeMakingChanges,
|
|
970
|
+
),
|
|
971
|
+
);
|
|
972
|
+
} else {
|
|
973
|
+
track.agent.beforeMakingChanges.forEach((item, i) => {
|
|
974
|
+
if (typeof item !== "string") {
|
|
975
|
+
errors.push(
|
|
976
|
+
createError(
|
|
977
|
+
"INVALID_VALUE",
|
|
978
|
+
"Track agent beforeMakingChanges items must be strings",
|
|
979
|
+
`${agentPath}.beforeMakingChanges[${i}]`,
|
|
980
|
+
item,
|
|
981
|
+
),
|
|
982
|
+
);
|
|
983
|
+
}
|
|
984
|
+
});
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
// Optional: constraints (array of strings)
|
|
989
|
+
if (track.agent.constraints !== undefined) {
|
|
990
|
+
if (!Array.isArray(track.agent.constraints)) {
|
|
991
|
+
errors.push(
|
|
992
|
+
createError(
|
|
993
|
+
"INVALID_VALUE",
|
|
994
|
+
"Track agent constraints must be an array",
|
|
995
|
+
`${agentPath}.constraints`,
|
|
996
|
+
track.agent.constraints,
|
|
997
|
+
),
|
|
998
|
+
);
|
|
999
|
+
} else {
|
|
1000
|
+
track.agent.constraints.forEach((item, i) => {
|
|
1001
|
+
if (typeof item !== "string") {
|
|
1002
|
+
errors.push(
|
|
1003
|
+
createError(
|
|
1004
|
+
"INVALID_VALUE",
|
|
1005
|
+
"Track agent constraints items must be strings",
|
|
1006
|
+
`${agentPath}.constraints[${i}]`,
|
|
1007
|
+
item,
|
|
1008
|
+
),
|
|
1009
|
+
);
|
|
1010
|
+
}
|
|
1011
|
+
});
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
// Error if old 'coreInstructions' field is still present
|
|
1016
|
+
if (track.agent.coreInstructions !== undefined) {
|
|
1017
|
+
errors.push(
|
|
1018
|
+
createError(
|
|
1019
|
+
"INVALID_FIELD",
|
|
1020
|
+
"Track agent 'coreInstructions' field is not supported. Use identity, priority, beforeMakingChanges, and constraints instead.",
|
|
1021
|
+
`${agentPath}.coreInstructions`,
|
|
1022
|
+
),
|
|
1023
|
+
);
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
|
|
540
1027
|
return { errors, warnings };
|
|
541
1028
|
}
|
|
542
1029
|
|
|
@@ -792,18 +1279,6 @@ function validateStage(stage, index) {
|
|
|
792
1279
|
);
|
|
793
1280
|
}
|
|
794
1281
|
|
|
795
|
-
// Mode is now inferred from availableTools - no longer required
|
|
796
|
-
// Validate availableTools array
|
|
797
|
-
if (!stage.availableTools || !Array.isArray(stage.availableTools)) {
|
|
798
|
-
errors.push(
|
|
799
|
-
createError(
|
|
800
|
-
"MISSING_REQUIRED",
|
|
801
|
-
"Stage missing availableTools array",
|
|
802
|
-
`${path}.availableTools`,
|
|
803
|
-
),
|
|
804
|
-
);
|
|
805
|
-
}
|
|
806
|
-
|
|
807
1282
|
if (!stage.handoffs || !Array.isArray(stage.handoffs)) {
|
|
808
1283
|
warnings.push(
|
|
809
1284
|
createWarning(
|
|
@@ -974,6 +1449,9 @@ export function validateAllData({
|
|
|
974
1449
|
const behaviourIds = new Set((behaviours || []).map((b) => b.id));
|
|
975
1450
|
const capabilityIds = new Set((capabilities || []).map((c) => c.id));
|
|
976
1451
|
|
|
1452
|
+
// Extract stage IDs for agent skill validation
|
|
1453
|
+
const requiredStageIds = (stages || []).map((s) => s.id);
|
|
1454
|
+
|
|
977
1455
|
// Validate skills
|
|
978
1456
|
if (!skills || skills.length === 0) {
|
|
979
1457
|
allErrors.push(
|
|
@@ -981,7 +1459,11 @@ export function validateAllData({
|
|
|
981
1459
|
);
|
|
982
1460
|
} else {
|
|
983
1461
|
skills.forEach((skill, index) => {
|
|
984
|
-
const { errors, warnings } = validateSkill(
|
|
1462
|
+
const { errors, warnings } = validateSkill(
|
|
1463
|
+
skill,
|
|
1464
|
+
index,
|
|
1465
|
+
requiredStageIds,
|
|
1466
|
+
);
|
|
985
1467
|
allErrors.push(...errors);
|
|
986
1468
|
allWarnings.push(...warnings);
|
|
987
1469
|
});
|
|
@@ -1036,6 +1518,12 @@ export function validateAllData({
|
|
|
1036
1518
|
});
|
|
1037
1519
|
}
|
|
1038
1520
|
|
|
1521
|
+
// Get track IDs for discipline validation
|
|
1522
|
+
const trackIdSet = new Set((tracks || []).map((t) => t.id));
|
|
1523
|
+
|
|
1524
|
+
// Get grade IDs for discipline and track validation
|
|
1525
|
+
const gradeIdSet = new Set((grades || []).map((g) => g.id));
|
|
1526
|
+
|
|
1039
1527
|
// Validate disciplines
|
|
1040
1528
|
if (!disciplines || disciplines.length === 0) {
|
|
1041
1529
|
allErrors.push(
|
|
@@ -1048,6 +1536,8 @@ export function validateAllData({
|
|
|
1048
1536
|
index,
|
|
1049
1537
|
skillIds,
|
|
1050
1538
|
behaviourIds,
|
|
1539
|
+
trackIdSet,
|
|
1540
|
+
gradeIdSet,
|
|
1051
1541
|
);
|
|
1052
1542
|
allErrors.push(...errors);
|
|
1053
1543
|
allWarnings.push(...warnings);
|
|
@@ -1075,12 +1565,6 @@ export function validateAllData({
|
|
|
1075
1565
|
// Get all skill IDs from disciplines for track validation
|
|
1076
1566
|
const disciplineSkillIds = getAllDisciplineSkillIds(disciplines || []);
|
|
1077
1567
|
|
|
1078
|
-
// Get discipline IDs for track validation
|
|
1079
|
-
const disciplineIdSet = new Set((disciplines || []).map((d) => d.id));
|
|
1080
|
-
|
|
1081
|
-
// Get grade IDs for track validation
|
|
1082
|
-
const gradeIdSet = new Set((grades || []).map((g) => g.id));
|
|
1083
|
-
|
|
1084
1568
|
// Validate tracks
|
|
1085
1569
|
if (!tracks || tracks.length === 0) {
|
|
1086
1570
|
allErrors.push(
|
|
@@ -1093,7 +1577,6 @@ export function validateAllData({
|
|
|
1093
1577
|
index,
|
|
1094
1578
|
disciplineSkillIds,
|
|
1095
1579
|
behaviourIds,
|
|
1096
|
-
disciplineIdSet,
|
|
1097
1580
|
gradeIdSet,
|
|
1098
1581
|
);
|
|
1099
1582
|
allErrors.push(...errors);
|
|
@@ -1383,3 +1866,199 @@ export function validateQuestionBank(questionBank, skills, behaviours) {
|
|
|
1383
1866
|
|
|
1384
1867
|
return createValidationResult(errors.length === 0, errors, warnings);
|
|
1385
1868
|
}
|
|
1869
|
+
|
|
1870
|
+
/**
|
|
1871
|
+
* Validate agent-specific data comprehensively
|
|
1872
|
+
* This validates cross-references between human and agent definitions
|
|
1873
|
+
* @param {Object} params - Validation parameters
|
|
1874
|
+
* @param {Object} params.humanData - Human data (disciplines, tracks, skills, behaviours, stages)
|
|
1875
|
+
* @param {Object} params.agentData - Agent-specific data (disciplines, tracks, behaviours with agent sections)
|
|
1876
|
+
* @returns {import('./levels.js').ValidationResult}
|
|
1877
|
+
*/
|
|
1878
|
+
export function validateAgentData({ humanData, agentData }) {
|
|
1879
|
+
const errors = [];
|
|
1880
|
+
const warnings = [];
|
|
1881
|
+
|
|
1882
|
+
const humanDisciplineIds = new Set(
|
|
1883
|
+
(humanData.disciplines || []).map((d) => d.id),
|
|
1884
|
+
);
|
|
1885
|
+
const humanTrackIds = new Set((humanData.tracks || []).map((t) => t.id));
|
|
1886
|
+
const humanBehaviourIds = new Set(
|
|
1887
|
+
(humanData.behaviours || []).map((b) => b.id),
|
|
1888
|
+
);
|
|
1889
|
+
const stageIds = new Set((humanData.stages || []).map((s) => s.id));
|
|
1890
|
+
|
|
1891
|
+
// Validate agent disciplines reference human disciplines
|
|
1892
|
+
for (const agentDiscipline of agentData.disciplines || []) {
|
|
1893
|
+
if (!humanDisciplineIds.has(agentDiscipline.id)) {
|
|
1894
|
+
errors.push(
|
|
1895
|
+
createError(
|
|
1896
|
+
"ORPHANED_AGENT",
|
|
1897
|
+
`Agent discipline '${agentDiscipline.id}' has no human definition`,
|
|
1898
|
+
`agentData.disciplines`,
|
|
1899
|
+
agentDiscipline.id,
|
|
1900
|
+
),
|
|
1901
|
+
);
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1904
|
+
// Validate required identity exists (spread from agent section by loader)
|
|
1905
|
+
if (!agentDiscipline.identity) {
|
|
1906
|
+
errors.push(
|
|
1907
|
+
createError(
|
|
1908
|
+
"MISSING_REQUIRED",
|
|
1909
|
+
`Agent discipline '${agentDiscipline.id}' missing identity`,
|
|
1910
|
+
`agentData.disciplines.${agentDiscipline.id}.identity`,
|
|
1911
|
+
),
|
|
1912
|
+
);
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1916
|
+
// Validate agent tracks reference human tracks
|
|
1917
|
+
for (const agentTrack of agentData.tracks || []) {
|
|
1918
|
+
if (!humanTrackIds.has(agentTrack.id)) {
|
|
1919
|
+
errors.push(
|
|
1920
|
+
createError(
|
|
1921
|
+
"ORPHANED_AGENT",
|
|
1922
|
+
`Agent track '${agentTrack.id}' has no human definition`,
|
|
1923
|
+
`agentData.tracks`,
|
|
1924
|
+
agentTrack.id,
|
|
1925
|
+
),
|
|
1926
|
+
);
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
// Validate agent behaviours reference human behaviours
|
|
1931
|
+
for (const agentBehaviour of agentData.behaviours || []) {
|
|
1932
|
+
if (!humanBehaviourIds.has(agentBehaviour.id)) {
|
|
1933
|
+
errors.push(
|
|
1934
|
+
createError(
|
|
1935
|
+
"ORPHANED_AGENT",
|
|
1936
|
+
`Agent behaviour '${agentBehaviour.id}' has no human definition`,
|
|
1937
|
+
`agentData.behaviours`,
|
|
1938
|
+
agentBehaviour.id,
|
|
1939
|
+
),
|
|
1940
|
+
);
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
// Validate required agent fields (spread from agent section by loader)
|
|
1944
|
+
if (!agentBehaviour.title) {
|
|
1945
|
+
errors.push(
|
|
1946
|
+
createError(
|
|
1947
|
+
"MISSING_REQUIRED",
|
|
1948
|
+
`Agent behaviour '${agentBehaviour.id}' missing title`,
|
|
1949
|
+
`agentData.behaviours.${agentBehaviour.id}.title`,
|
|
1950
|
+
),
|
|
1951
|
+
);
|
|
1952
|
+
}
|
|
1953
|
+
if (!agentBehaviour.workingStyle) {
|
|
1954
|
+
errors.push(
|
|
1955
|
+
createError(
|
|
1956
|
+
"MISSING_REQUIRED",
|
|
1957
|
+
`Agent behaviour '${agentBehaviour.id}' missing workingStyle`,
|
|
1958
|
+
`agentData.behaviours.${agentBehaviour.id}.workingStyle`,
|
|
1959
|
+
),
|
|
1960
|
+
);
|
|
1961
|
+
}
|
|
1962
|
+
}
|
|
1963
|
+
|
|
1964
|
+
// Validate skills with agent sections have complete stage coverage
|
|
1965
|
+
const skillsWithAgent = (humanData.skills || []).filter((s) => s.agent);
|
|
1966
|
+
const requiredStages = ["plan", "code", "review"];
|
|
1967
|
+
|
|
1968
|
+
for (const skill of skillsWithAgent) {
|
|
1969
|
+
const stages = skill.agent.stages || {};
|
|
1970
|
+
const missingStages = requiredStages.filter((stage) => !stages[stage]);
|
|
1971
|
+
|
|
1972
|
+
if (missingStages.length > 0) {
|
|
1973
|
+
warnings.push(
|
|
1974
|
+
createWarning(
|
|
1975
|
+
"INCOMPLETE_STAGES",
|
|
1976
|
+
`Skill '${skill.id}' agent section missing stages: ${missingStages.join(", ")}`,
|
|
1977
|
+
`skills.${skill.id}.agent.stages`,
|
|
1978
|
+
),
|
|
1979
|
+
);
|
|
1980
|
+
}
|
|
1981
|
+
|
|
1982
|
+
// Validate each stage has required fields
|
|
1983
|
+
for (const [stageId, stageData] of Object.entries(stages)) {
|
|
1984
|
+
if (!stageData.focus) {
|
|
1985
|
+
errors.push(
|
|
1986
|
+
createError(
|
|
1987
|
+
"MISSING_REQUIRED",
|
|
1988
|
+
`Skill '${skill.id}' agent stage '${stageId}' missing focus`,
|
|
1989
|
+
`skills.${skill.id}.agent.stages.${stageId}.focus`,
|
|
1990
|
+
),
|
|
1991
|
+
);
|
|
1992
|
+
}
|
|
1993
|
+
if (
|
|
1994
|
+
!stageData.activities ||
|
|
1995
|
+
!Array.isArray(stageData.activities) ||
|
|
1996
|
+
stageData.activities.length === 0
|
|
1997
|
+
) {
|
|
1998
|
+
errors.push(
|
|
1999
|
+
createError(
|
|
2000
|
+
"MISSING_REQUIRED",
|
|
2001
|
+
`Skill '${skill.id}' agent stage '${stageId}' missing or empty activities`,
|
|
2002
|
+
`skills.${skill.id}.agent.stages.${stageId}.activities`,
|
|
2003
|
+
),
|
|
2004
|
+
);
|
|
2005
|
+
}
|
|
2006
|
+
if (
|
|
2007
|
+
!stageData.ready ||
|
|
2008
|
+
!Array.isArray(stageData.ready) ||
|
|
2009
|
+
stageData.ready.length === 0
|
|
2010
|
+
) {
|
|
2011
|
+
errors.push(
|
|
2012
|
+
createError(
|
|
2013
|
+
"MISSING_REQUIRED",
|
|
2014
|
+
`Skill '${skill.id}' agent stage '${stageId}' missing or empty ready criteria`,
|
|
2015
|
+
`skills.${skill.id}.agent.stages.${stageId}.ready`,
|
|
2016
|
+
),
|
|
2017
|
+
);
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
|
|
2022
|
+
// Validate stage handoff targets exist
|
|
2023
|
+
for (const stage of humanData.stages || []) {
|
|
2024
|
+
if (stage.handoffs) {
|
|
2025
|
+
for (const handoff of stage.handoffs) {
|
|
2026
|
+
const targetId = handoff.targetStage || handoff.target;
|
|
2027
|
+
if (targetId && !stageIds.has(targetId)) {
|
|
2028
|
+
errors.push(
|
|
2029
|
+
createError(
|
|
2030
|
+
"INVALID_REFERENCE",
|
|
2031
|
+
`Stage '${stage.id}' handoff references unknown stage '${targetId}'`,
|
|
2032
|
+
`stages.${stage.id}.handoffs`,
|
|
2033
|
+
targetId,
|
|
2034
|
+
),
|
|
2035
|
+
);
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
}
|
|
2040
|
+
|
|
2041
|
+
// Summary statistics as warnings (informational)
|
|
2042
|
+
const stats = {
|
|
2043
|
+
agentDisciplines: (agentData.disciplines || []).length,
|
|
2044
|
+
agentTracks: (agentData.tracks || []).length,
|
|
2045
|
+
agentBehaviours: (agentData.behaviours || []).length,
|
|
2046
|
+
skillsWithAgent: skillsWithAgent.length,
|
|
2047
|
+
skillsWithCompleteStages: skillsWithAgent.filter((s) => {
|
|
2048
|
+
const stages = s.agent.stages || {};
|
|
2049
|
+
return requiredStages.every((stage) => stages[stage]);
|
|
2050
|
+
}).length,
|
|
2051
|
+
};
|
|
2052
|
+
|
|
2053
|
+
if (stats.skillsWithCompleteStages < stats.skillsWithAgent) {
|
|
2054
|
+
warnings.push(
|
|
2055
|
+
createWarning(
|
|
2056
|
+
"INCOMPLETE_COVERAGE",
|
|
2057
|
+
`${stats.skillsWithCompleteStages}/${stats.skillsWithAgent} skills have complete stage coverage (plan, code, review)`,
|
|
2058
|
+
"agentData",
|
|
2059
|
+
),
|
|
2060
|
+
);
|
|
2061
|
+
}
|
|
2062
|
+
|
|
2063
|
+
return createValidationResult(errors.length === 0, errors, warnings);
|
|
2064
|
+
}
|