@forwardimpact/pathway 0.1.0 → 0.2.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.
Files changed (131) hide show
  1. package/app/commands/agent.js +109 -21
  2. package/app/commands/command-factory.js +3 -3
  3. package/app/commands/interview.js +14 -7
  4. package/app/commands/job.js +43 -29
  5. package/app/commands/progress.js +14 -7
  6. package/app/commands/serve.js +5 -0
  7. package/app/commands/stage.js +0 -10
  8. package/app/commands/track.js +5 -8
  9. package/app/components/builder.js +111 -27
  10. package/app/css/components/surfaces.css +16 -0
  11. package/app/formatters/agent/profile.js +113 -87
  12. package/app/formatters/agent/skill.js +64 -31
  13. package/app/formatters/behaviour/dom.js +3 -0
  14. package/app/formatters/behaviour/microdata.js +106 -0
  15. package/app/formatters/discipline/dom.js +28 -1
  16. package/app/formatters/discipline/microdata.js +117 -0
  17. package/app/formatters/discipline/shared.js +49 -8
  18. package/app/formatters/driver/dom.js +3 -0
  19. package/app/formatters/driver/microdata.js +91 -0
  20. package/app/formatters/grade/dom.js +3 -0
  21. package/app/formatters/grade/microdata.js +151 -0
  22. package/app/formatters/index.js +32 -1
  23. package/app/formatters/interview/shared.js +13 -8
  24. package/app/formatters/job/description.js +5 -3
  25. package/app/formatters/json-ld.js +242 -0
  26. package/app/formatters/microdata-shared.js +184 -0
  27. package/app/formatters/progress/shared.js +14 -11
  28. package/app/formatters/skill/dom.js +3 -0
  29. package/app/formatters/skill/microdata.js +151 -0
  30. package/app/formatters/stage/dom.js +3 -18
  31. package/app/formatters/stage/microdata.js +110 -0
  32. package/app/formatters/stage/shared.js +0 -27
  33. package/app/formatters/track/dom.js +5 -30
  34. package/app/formatters/track/markdown.js +2 -25
  35. package/app/formatters/track/microdata.js +111 -0
  36. package/app/formatters/track/shared.js +6 -58
  37. package/app/handout-main.js +26 -12
  38. package/app/index.html +11 -0
  39. package/app/lib/card-mappers.js +17 -12
  40. package/app/lib/job-cache.js +12 -9
  41. package/app/lib/template-loader.js +66 -0
  42. package/app/lib/yaml-loader.js +25 -8
  43. package/app/main.js +8 -4
  44. package/app/model/agent.js +158 -130
  45. package/app/model/checklist.js +57 -91
  46. package/app/model/derivation.js +135 -68
  47. package/app/model/index-generator.js +1 -7
  48. package/app/model/job.js +19 -13
  49. package/app/model/levels.js +20 -12
  50. package/app/model/loader.js +41 -17
  51. package/app/model/matching.js +33 -3
  52. package/app/model/profile.js +38 -45
  53. package/app/model/schema-validation.js +438 -0
  54. package/app/model/validation.js +747 -68
  55. package/app/pages/agent-builder.js +119 -25
  56. package/app/pages/assessment-results.js +10 -4
  57. package/app/pages/discipline.js +36 -6
  58. package/app/pages/driver.js +9 -47
  59. package/app/pages/interview-builder.js +3 -1
  60. package/app/pages/interview.js +15 -4
  61. package/app/pages/job-builder.js +4 -1
  62. package/app/pages/job.js +15 -4
  63. package/app/pages/landing.js +10 -10
  64. package/app/pages/progress-builder.js +3 -1
  65. package/app/pages/progress.js +72 -21
  66. package/app/pages/stage.js +3 -126
  67. package/app/slide-main.js +45 -17
  68. package/app/slides/index.js +3 -1
  69. package/app/slides/overview.js +40 -4
  70. package/app/slides/progress.js +4 -2
  71. package/bin/pathway.js +18 -64
  72. package/examples/agents/.claude/skills/architecture-design/SKILL.md +58 -16
  73. package/examples/agents/.claude/skills/cloud-platforms/SKILL.md +59 -18
  74. package/examples/agents/.claude/skills/code-quality-review/SKILL.md +58 -17
  75. package/examples/agents/.claude/skills/devops-cicd/SKILL.md +64 -18
  76. package/examples/agents/.claude/skills/full-stack-development/SKILL.md +59 -15
  77. package/examples/agents/.claude/skills/sre-practices/SKILL.md +64 -18
  78. package/examples/agents/.claude/skills/technical-debt-management/SKILL.md +58 -17
  79. package/examples/agents/.github/agents/se-platform-code.agent.md +39 -88
  80. package/examples/agents/.github/agents/se-platform-plan.agent.md +41 -88
  81. package/examples/agents/.github/agents/se-platform-review.agent.md +38 -15
  82. package/examples/agents/.vscode/settings.json +1 -1
  83. package/examples/behaviours/outcome_ownership.yaml +1 -2
  84. package/examples/behaviours/polymathic_knowledge.yaml +1 -2
  85. package/examples/behaviours/precise_communication.yaml +1 -2
  86. package/examples/behaviours/relentless_curiosity.yaml +1 -2
  87. package/examples/behaviours/systems_thinking.yaml +1 -2
  88. package/examples/capabilities/business.yaml +80 -142
  89. package/examples/capabilities/delivery.yaml +155 -219
  90. package/examples/capabilities/people.yaml +2 -34
  91. package/examples/capabilities/reliability.yaml +161 -80
  92. package/examples/capabilities/scale.yaml +234 -252
  93. package/examples/copilot-setup-steps.yaml +25 -0
  94. package/examples/devcontainer.yaml +21 -0
  95. package/examples/disciplines/_index.yaml +1 -0
  96. package/examples/disciplines/data_engineering.yaml +14 -12
  97. package/examples/disciplines/engineering_management.yaml +63 -0
  98. package/examples/disciplines/software_engineering.yaml +14 -12
  99. package/examples/drivers.yaml +1 -4
  100. package/examples/framework.yaml +1 -2
  101. package/examples/grades.yaml +1 -3
  102. package/examples/questions/behaviours/outcome_ownership.yaml +1 -2
  103. package/examples/questions/behaviours/polymathic_knowledge.yaml +1 -2
  104. package/examples/questions/behaviours/precise_communication.yaml +1 -2
  105. package/examples/questions/behaviours/relentless_curiosity.yaml +1 -2
  106. package/examples/questions/behaviours/systems_thinking.yaml +1 -2
  107. package/examples/questions/skills/architecture_design.yaml +1 -2
  108. package/examples/questions/skills/cloud_platforms.yaml +1 -2
  109. package/examples/questions/skills/code_quality.yaml +1 -2
  110. package/examples/questions/skills/data_modeling.yaml +1 -2
  111. package/examples/questions/skills/devops.yaml +1 -2
  112. package/examples/questions/skills/full_stack_development.yaml +1 -2
  113. package/examples/questions/skills/sre_practices.yaml +1 -2
  114. package/examples/questions/skills/stakeholder_management.yaml +1 -2
  115. package/examples/questions/skills/team_collaboration.yaml +1 -2
  116. package/examples/questions/skills/technical_writing.yaml +1 -2
  117. package/examples/self-assessments.yaml +1 -3
  118. package/examples/stages.yaml +101 -46
  119. package/examples/tracks/_index.yaml +0 -1
  120. package/examples/tracks/platform.yaml +8 -13
  121. package/examples/tracks/sre.yaml +8 -18
  122. package/examples/vscode-settings.yaml +2 -7
  123. package/package.json +9 -3
  124. package/templates/agent.template.md +65 -0
  125. package/templates/skill.template.md +28 -0
  126. package/examples/agents/.claude/skills/data-modeling/SKILL.md +0 -99
  127. package/examples/agents/.claude/skills/developer-experience/SKILL.md +0 -99
  128. package/examples/agents/.claude/skills/knowledge-management/SKILL.md +0 -100
  129. package/examples/agents/.claude/skills/pattern-generalization/SKILL.md +0 -102
  130. package/examples/agents/.claude/skills/technical-writing/SKILL.md +0 -129
  131. package/examples/tracks/manager.yaml +0 -53
@@ -104,8 +104,9 @@ export function toKebabCase(id) {
104
104
  /**
105
105
  * Derive agent skills using the unified profile system
106
106
  * Returns skills sorted by level (highest first) for the given discipline × track
107
- * Excludes human-only skills (those requiring human presence/experience)
108
- * Excludes broad skills unless they're in a capability with positive track modifier
107
+ * Excludes human-only skills and keeps only skills at the highest derived level.
108
+ * This approach respects track modifiers—a broad skill boosted to the same level
109
+ * as primary skills will be included.
109
110
  * @param {Object} params - Parameters
110
111
  * @param {Object} params.discipline - Human discipline definition
111
112
  * @param {Object} params.track - Human track definition
@@ -123,7 +124,7 @@ export function deriveAgentSkills({ discipline, track, grade, skills }) {
123
124
  });
124
125
 
125
126
  // Apply agent-specific filtering and sorting
126
- const filtered = filterSkillsForAgent(skillMatrix, track);
127
+ const filtered = filterSkillsForAgent(skillMatrix);
127
128
  return sortByLevelDescending(filtered);
128
129
  }
129
130
 
@@ -224,28 +225,110 @@ function buildWorkingStyleFromBehaviours(
224
225
  return sections.join("\n");
225
226
  }
226
227
 
228
+ /**
229
+ * Stage ID to display name and next stage mapping
230
+ */
231
+ const STAGE_INFO = {
232
+ plan: { name: "Plan", nextStage: "Code" },
233
+ code: { name: "Code", nextStage: "Review" },
234
+ review: { name: "Review", nextStage: "Complete" },
235
+ };
236
+
227
237
  /**
228
238
  * Generate SKILL.md content from skill data
229
- * @param {Object} skillData - Skill with agent section
230
- * @returns {Object} Skill with frontmatter, body, and dirname
239
+ * @param {Object} skillData - Skill with agent section containing stages
240
+ * @returns {Object} Skill with frontmatter, title, stages array, reference, dirname
231
241
  */
232
242
  export function generateSkillMd(skillData) {
233
- const { agent } = skillData;
243
+ const { agent, name } = skillData;
234
244
 
235
245
  if (!agent) {
236
246
  throw new Error(`Skill ${skillData.id} has no agent section`);
237
247
  }
238
248
 
249
+ if (!agent.stages) {
250
+ throw new Error(`Skill ${skillData.id} agent section missing stages`);
251
+ }
252
+
253
+ // Transform stages object to array for template rendering
254
+ const stagesArray = Object.entries(agent.stages).map(
255
+ ([stageId, stageData]) => {
256
+ const info = STAGE_INFO[stageId] || {
257
+ name: stageId,
258
+ nextStage: "Next",
259
+ };
260
+ return {
261
+ stageId,
262
+ stageName: info.name,
263
+ nextStageName: info.nextStage,
264
+ focus: stageData.focus,
265
+ activities: stageData.activities || [],
266
+ ready: stageData.ready || [],
267
+ };
268
+ },
269
+ );
270
+
271
+ // Sort stages in order: plan, code, review
272
+ const stageOrder = ["plan", "code", "review"];
273
+ stagesArray.sort(
274
+ (a, b) => stageOrder.indexOf(a.stageId) - stageOrder.indexOf(b.stageId),
275
+ );
276
+
239
277
  return {
240
278
  frontmatter: {
241
279
  name: agent.name,
242
280
  description: agent.description.trim(),
243
281
  },
244
- body: agent.body.trim(),
282
+ title: name,
283
+ stages: stagesArray,
284
+ reference: agent.reference ? agent.reference.trim() : "",
245
285
  dirname: agent.name,
246
286
  };
247
287
  }
248
288
 
289
+ /**
290
+ * Estimate total character length of bodyData fields
291
+ * @param {Object} bodyData - Structured profile body data
292
+ * @returns {number} Estimated character count
293
+ */
294
+ function estimateBodyDataLength(bodyData) {
295
+ let length = 0;
296
+
297
+ // String fields
298
+ const stringFields = [
299
+ "title",
300
+ "stageDescription",
301
+ "identity",
302
+ "priority",
303
+ "delegation",
304
+ "operationalContext",
305
+ "workingStyle",
306
+ "beforeHandoff",
307
+ ];
308
+ for (const field of stringFields) {
309
+ if (bodyData[field]) {
310
+ length += bodyData[field].length;
311
+ }
312
+ }
313
+
314
+ // Array fields
315
+ if (bodyData.capabilities) {
316
+ length += bodyData.capabilities.join(", ").length;
317
+ }
318
+ if (bodyData.beforeMakingChanges) {
319
+ for (const item of bodyData.beforeMakingChanges) {
320
+ length += item.text.length + 5; // +5 for "1. " prefix
321
+ }
322
+ }
323
+ if (bodyData.constraints) {
324
+ for (const c of bodyData.constraints) {
325
+ length += c.length + 2; // +2 for "- " prefix
326
+ }
327
+ }
328
+
329
+ return length;
330
+ }
331
+
249
332
  /**
250
333
  * Validate agent profile against spec constraints
251
334
  * @param {Object} profile - Generated profile
@@ -266,9 +349,10 @@ export function validateAgentProfile(profile) {
266
349
  }
267
350
  }
268
351
 
269
- // Body length limit (30,000 chars)
270
- if (profile.body.length > 30000) {
271
- errors.push(`Body exceeds 30,000 character limit (${profile.body.length})`);
352
+ // Body length limit (30,000 chars) - estimate from bodyData fields
353
+ const bodyLength = estimateBodyDataLength(profile.bodyData);
354
+ if (bodyLength > 30000) {
355
+ errors.push(`Body exceeds 30,000 character limit (${bodyLength})`);
272
356
  }
273
357
 
274
358
  // Tools format
@@ -322,17 +406,6 @@ export function validateAgentSkill(skill) {
322
406
  // Stage-Based Agent Generation
323
407
  // =============================================================================
324
408
 
325
- /**
326
- * Derive tools for a stage-based agent
327
- * Stages define the authoritative tool set for each lifecycle phase
328
- * @param {Object} params - Parameters
329
- * @param {Object} params.stage - Stage definition from stages.yaml
330
- * @returns {string[]} Array of tool names
331
- */
332
- export function deriveAgentTools({ stage }) {
333
- return stage.availableTools || [];
334
- }
335
-
336
409
  /**
337
410
  * Derive handoff buttons for a stage-based agent
338
411
  * Generates handoff button definitions from stage.handoffs with rich prompts
@@ -349,8 +422,9 @@ export function deriveHandoffs({ stage, discipline, track, stages }) {
349
422
  return [];
350
423
  }
351
424
 
352
- // Build base name for target agents
353
- const baseName = `${toKebabCase(discipline.id)}-${toKebabCase(track.id)}`;
425
+ // Build base name for target agents (matches filename without .agent.md)
426
+ const abbrev = getDisciplineAbbreviation(discipline.id);
427
+ const baseName = `${abbrev}-${toKebabCase(track.id)}`;
354
428
 
355
429
  return stage.handoffs.map((handoff) => {
356
430
  // Find the target stage to get its entry criteria
@@ -390,19 +464,16 @@ export function deriveHandoffs({ stage, discipline, track, stages }) {
390
464
  /**
391
465
  * Get the handoff type for a stage (used for checklist derivation)
392
466
  * @param {string} stageId - Stage ID (plan, code, review)
393
- * @returns {string|null} Handoff type or null
467
+ * @returns {string|null} Stage ID for checklist or null
394
468
  */
395
- function getHandoffForStage(stageId) {
396
- const handoffMap = {
397
- plan: "plan_to_code",
398
- code: "code_to_review",
399
- review: null, // Review stage doesn't need a checklist
400
- };
401
- return handoffMap[stageId] || null;
469
+ function getChecklistStage(stageId) {
470
+ // Plan and code stages have checklists, review doesn't
471
+ return stageId === "review" ? null : stageId;
402
472
  }
403
473
 
404
474
  /**
405
- * Build the profile body for a stage-based agent
475
+ * Build the profile body data for a stage-based agent
476
+ * Returns structured data for template rendering
406
477
  * @param {Object} params - Parameters
407
478
  * @param {Object} params.stage - Stage definition
408
479
  * @param {Object} params.humanDiscipline - Human discipline definition
@@ -413,9 +484,9 @@ function getHandoffForStage(stageId) {
413
484
  * @param {Array} params.derivedBehaviours - Behaviours sorted by maturity
414
485
  * @param {Array} params.agentBehaviours - Agent behaviour definitions
415
486
  * @param {string} params.checklistMarkdown - Pre-formatted checklist markdown
416
- * @returns {string} Profile body markdown
487
+ * @returns {Object} Structured profile body data
417
488
  */
418
- function buildStageProfileBody({
489
+ function buildStageProfileBodyData({
419
490
  stage,
420
491
  humanDiscipline,
421
492
  humanTrack,
@@ -428,103 +499,64 @@ function buildStageProfileBody({
428
499
  }) {
429
500
  const name = `${humanDiscipline.specialization || humanDiscipline.name} - ${humanTrack.name}`;
430
501
  const stageName = stage.name.charAt(0).toUpperCase() + stage.name.slice(1);
431
- const sections = [];
432
-
433
- // Title with stage indicator
434
- sections.push(`# ${name} - ${stageName} Agent`);
435
- sections.push("");
436
502
 
437
- // Stage description
438
- sections.push(stage.description);
439
- sections.push("");
440
-
441
- // Core Identity
442
- sections.push("## Core Identity");
443
- sections.push("");
444
-
445
- // Use track coreInstructions if available, with template substitution
446
- const rawInstructions =
447
- agentTrack.coreInstructions || agentDiscipline.coreInstructions;
448
- const coreInstructions = substituteTemplateVars(
449
- rawInstructions,
450
- humanDiscipline,
451
- );
452
- sections.push(coreInstructions.trim());
453
- sections.push("");
503
+ // Build identity - prefer track, fall back to discipline
504
+ const rawIdentity = agentTrack.identity || agentDiscipline.identity;
505
+ const identity = substituteTemplateVars(rawIdentity, humanDiscipline);
506
+
507
+ // Build priority - prefer track, fall back to discipline (optional)
508
+ const rawPriority = agentTrack.priority || agentDiscipline.priority;
509
+ const priority = rawPriority
510
+ ? substituteTemplateVars(rawPriority, humanDiscipline)
511
+ : null;
512
+
513
+ // Build beforeMakingChanges list - prefer track, fall back to discipline
514
+ const rawSteps =
515
+ agentTrack.beforeMakingChanges || agentDiscipline.beforeMakingChanges || [];
516
+ const beforeMakingChanges = rawSteps.map((text, i) => ({
517
+ index: i + 1,
518
+ text: substituteTemplateVars(text, humanDiscipline),
519
+ }));
520
+
521
+ // Delegation (from discipline only, optional)
522
+ const rawDelegation = agentDiscipline.delegation;
523
+ const delegation = rawDelegation
524
+ ? substituteTemplateVars(rawDelegation, humanDiscipline)
525
+ : null;
454
526
 
455
527
  // Primary capabilities from derived skills
456
- const topSkills = derivedSkills.slice(0, 6);
457
- if (topSkills.length > 0) {
458
- sections.push("Your primary capabilities:");
459
- for (const skill of topSkills) {
460
- sections.push(`- ${skill.skillName}`);
461
- }
462
- sections.push("");
463
- }
528
+ const capabilities = derivedSkills.slice(0, 6).map((s) => s.skillName);
464
529
 
465
530
  // Operational Context - use track's roleContext (shared with human job descriptions)
466
- sections.push("## Operational Context");
467
- sections.push("");
468
- sections.push(humanTrack.roleContext.trim());
469
- sections.push("");
531
+ const operationalContext = humanTrack.roleContext.trim();
470
532
 
471
- // Working Style from derived behaviours
533
+ // Working Style from derived behaviours (still markdown for now)
472
534
  const workingStyle = buildWorkingStyleFromBehaviours(
473
535
  derivedBehaviours,
474
536
  agentBehaviours,
475
537
  3,
476
538
  );
477
- sections.push(workingStyle);
478
-
479
- // Before Handoff (if provided)
480
- if (checklistMarkdown) {
481
- sections.push("## Before Handoff");
482
- sections.push("");
483
- sections.push(
484
- "Before offering a handoff, verify and summarize completion of these items:",
485
- );
486
- sections.push("");
487
- sections.push(checklistMarkdown);
488
- sections.push("");
489
- sections.push(
490
- "When verified, summarize what was accomplished then offer the handoff.",
491
- );
492
- sections.push("If items are incomplete, explain what remains.");
493
- sections.push("");
494
- }
495
-
496
- // Return Format section
497
- sections.push("## Return Format");
498
- sections.push("");
499
- sections.push(
500
- "When completing work (for handoff or as a subagent), provide:",
501
- );
502
- sections.push("");
503
- sections.push("1. **Work completed**: What was accomplished");
504
- sections.push(
505
- "2. **Checklist status**: Items verified from Before Handoff section",
506
- );
507
- sections.push(
508
- "3. **Recommendation**: Ready for next stage, or needs more work",
509
- );
510
- sections.push("");
511
539
 
512
540
  // Constraints (stage + discipline + track)
513
- const allConstraints = [
541
+ const constraints = [
514
542
  ...(stage.constraints || []),
515
543
  ...(agentDiscipline.constraints || []),
516
544
  ...(agentTrack.constraints || []),
517
545
  ];
518
- if (allConstraints.length > 0) {
519
- sections.push("## Constraints");
520
- sections.push("");
521
- for (const constraint of allConstraints) {
522
- sections.push(`- ${constraint}`);
523
- }
524
- sections.push("");
525
- }
526
546
 
527
- return sections.join("\n");
547
+ return {
548
+ title: `${name} - ${stageName} Agent`,
549
+ stageDescription: stage.description,
550
+ identity: identity.trim(),
551
+ priority: priority ? priority.trim() : null,
552
+ capabilities,
553
+ beforeMakingChanges,
554
+ delegation: delegation ? delegation.trim() : null,
555
+ operationalContext,
556
+ workingStyle,
557
+ beforeHandoff: checklistMarkdown || null,
558
+ constraints,
559
+ };
528
560
  }
529
561
 
530
562
  /**
@@ -540,7 +572,7 @@ function buildStageProfileBody({
540
572
  * @param {Array} params.agentBehaviours - Agent behaviour definitions
541
573
  * @param {Object} params.agentDiscipline - Agent discipline definition
542
574
  * @param {Object} params.agentTrack - Agent track definition
543
- * @param {Array} params.capabilities - Capabilities with checklists
575
+ * @param {Array} params.capabilities - Capabilities for checklist grouping
544
576
  * @param {Array} params.stages - All stages (for handoff entry criteria)
545
577
  * @returns {Object} Agent definition with skills, behaviours, tools, handoffs, constraints, checklist
546
578
  */
@@ -572,9 +604,6 @@ export function deriveStageAgent({
572
604
  behaviours,
573
605
  });
574
606
 
575
- // Derive tools from stage
576
- const tools = deriveAgentTools({ stage });
577
-
578
607
  // Derive handoffs from stage
579
608
  const handoffs = deriveHandoffs({
580
609
  stage,
@@ -584,12 +613,13 @@ export function deriveStageAgent({
584
613
  });
585
614
 
586
615
  // Derive checklist if applicable
587
- const handoffType = getHandoffForStage(stage.id);
616
+ const checklistStage = getChecklistStage(stage.id);
588
617
  let checklist = [];
589
- if (handoffType && capabilities) {
618
+ if (checklistStage && capabilities) {
590
619
  checklist = deriveChecklist({
591
- handoff: handoffType,
620
+ stageId: checklistStage,
592
621
  skillMatrix: derivedSkills,
622
+ skills,
593
623
  capabilities,
594
624
  });
595
625
  }
@@ -600,7 +630,6 @@ export function deriveStageAgent({
600
630
  track,
601
631
  derivedSkills,
602
632
  derivedBehaviours,
603
- tools,
604
633
  handoffs,
605
634
  constraints: [
606
635
  ...(stage.constraints || []),
@@ -616,7 +645,7 @@ export function deriveStageAgent({
616
645
 
617
646
  /**
618
647
  * Generate a stage-specific agent profile (.agent.md)
619
- * Produces the complete profile with frontmatter, body, and filename
648
+ * Produces the complete profile with frontmatter, bodyData, and filename
620
649
  * @param {Object} params - Parameters
621
650
  * @param {Object} params.discipline - Human discipline definition
622
651
  * @param {Object} params.track - Human track definition
@@ -629,7 +658,7 @@ export function deriveStageAgent({
629
658
  * @param {Object} params.agentTrack - Agent track definition
630
659
  * @param {Array} params.capabilities - Capabilities with checklists
631
660
  * @param {Array} params.stages - All stages (for handoff entry criteria)
632
- * @returns {Object} Profile with frontmatter, body, and filename
661
+ * @returns {Object} Profile with frontmatter, bodyData, and filename
633
662
  */
634
663
  export function generateStageAgentProfile({
635
664
  discipline,
@@ -659,10 +688,10 @@ export function generateStageAgentProfile({
659
688
  stages,
660
689
  });
661
690
 
662
- // Build names
663
- const fullName = `${toKebabCase(discipline.id)}-${toKebabCase(track.id)}-${stage.id}`;
691
+ // Build names (abbreviated form used consistently for filename, name, and handoffs)
664
692
  const abbrev = getDisciplineAbbreviation(discipline.id);
665
- const filename = `${abbrev}-${toKebabCase(track.id)}-${stage.id}.agent.md`;
693
+ const fullName = `${abbrev}-${toKebabCase(track.id)}-${stage.id}`;
694
+ const filename = `${fullName}.agent.md`;
666
695
 
667
696
  // Build description
668
697
  const disciplineDesc = discipline.description.trim().split("\n")[0];
@@ -672,8 +701,8 @@ export function generateStageAgentProfile({
672
701
  // Format checklist as markdown
673
702
  const checklistMarkdown = formatChecklistMarkdown(agent.checklist);
674
703
 
675
- // Build profile body
676
- const body = buildStageProfileBody({
704
+ // Build structured profile body data
705
+ const bodyData = buildStageProfileBodyData({
677
706
  stage,
678
707
  humanDiscipline: discipline,
679
708
  humanTrack: track,
@@ -689,14 +718,13 @@ export function generateStageAgentProfile({
689
718
  const frontmatter = {
690
719
  name: fullName,
691
720
  description,
692
- tools: agent.tools,
693
721
  infer: true,
694
722
  ...(agent.handoffs.length > 0 && { handoffs: agent.handoffs }),
695
723
  };
696
724
 
697
725
  return {
698
726
  frontmatter,
699
- body,
727
+ bodyData,
700
728
  filename,
701
729
  };
702
730
  }
@@ -1,116 +1,81 @@
1
1
  /**
2
2
  * Checklist Derivation
3
3
  *
4
- * Checklists are derived from:
5
- * Checklist = Handoff × Skills Matrix × Capability Checklists
4
+ * Checklists are derived from skills with agent.stages.{stage}.ready criteria.
5
+ * Each skill defines its own readiness criteria for stage transitions.
6
6
  *
7
- * The skill matrix determines which capability levels are relevant,
8
- * and the capability checklists provide items for each level.
7
+ * Checklist = Stage × Skill Matrix × Skill Ready Criteria
9
8
  */
10
9
 
11
- import { SKILL_LEVEL_ORDER, getSkillLevelIndex } from "./levels.js";
12
-
13
10
  /**
14
- * Get the maximum skill level for a capability from the skill matrix
15
- * @param {Array} skillMatrix - Derived skill matrix with entries like { skillId, level, capability }
16
- * @param {string} capabilityId - Capability ID to check
17
- * @returns {string|null} Maximum skill level or null if no skills in this capability
11
+ * Map from stage ID to the stage whose ready criteria should be shown
12
+ * (i.e., what must be ready before leaving this stage)
18
13
  */
19
- export function getMaxCapabilityLevel(skillMatrix, capabilityId) {
20
- const skillsInCapability = skillMatrix.filter(
21
- (entry) => entry.capability === capabilityId,
22
- );
23
-
24
- if (skillsInCapability.length === 0) {
25
- return null;
26
- }
27
-
28
- // Find the highest level among skills in this capability
29
- let maxIndex = -1;
30
- let maxLevel = null;
31
-
32
- for (const entry of skillsInCapability) {
33
- const index = getSkillLevelIndex(entry.level);
34
- if (index > maxIndex) {
35
- maxIndex = index;
36
- maxLevel = entry.level;
37
- }
38
- }
39
-
40
- return maxLevel;
41
- }
14
+ const STAGE_TO_HANDOFF = {
15
+ plan: "plan", // Show plan.ready before leaving plan
16
+ code: "code", // Show code.ready before leaving code
17
+ review: "review", // Show review.ready (completion criteria)
18
+ };
42
19
 
43
20
  /**
44
- * Get all checklist items up to and including a given level
45
- * @param {Object} checklists - Capability checklists for a specific handoff
46
- * @param {string} maxLevel - Maximum level to include
47
- * @returns {string[]} Array of checklist items
21
+ * Derive checklist items for a specific stage
22
+ * Returns skills grouped by capability with their ready criteria
23
+ *
24
+ * @param {Object} params
25
+ * @param {string} params.stageId - Current stage (plan, code, review)
26
+ * @param {Array} params.skillMatrix - Derived skill matrix with skill details
27
+ * @param {Array} params.skills - All skills (to look up agent.stages)
28
+ * @param {Array} params.capabilities - All capabilities (for emoji lookup)
29
+ * @returns {Array<{skill: Object, capability: Object, items: string[]}>} Checklist items grouped by skill
48
30
  */
49
- function getChecklistItemsUpToLevel(checklists, maxLevel) {
50
- if (!checklists || !maxLevel) {
31
+ export function deriveChecklist({
32
+ stageId,
33
+ skillMatrix,
34
+ skills,
35
+ capabilities,
36
+ }) {
37
+ const targetStage = STAGE_TO_HANDOFF[stageId];
38
+ if (!targetStage) {
51
39
  return [];
52
40
  }
53
41
 
54
- const maxIndex = getSkillLevelIndex(maxLevel);
55
- const items = [];
42
+ // Build skill lookup
43
+ const skillById = new Map(skills.map((s) => [s.id, s]));
56
44
 
57
- // Include items from all levels up to and including maxLevel
58
- for (const level of SKILL_LEVEL_ORDER) {
59
- const levelIndex = getSkillLevelIndex(level);
60
- if (levelIndex <= maxIndex && checklists[level]) {
61
- items.push(...checklists[level]);
62
- }
63
- }
64
-
65
- return items;
66
- }
45
+ // Build capability lookup
46
+ const capabilityById = new Map(capabilities.map((c) => [c.id, c]));
67
47
 
68
- /**
69
- * Derive checklist items for a specific handoff
70
- *
71
- * @param {Object} params
72
- * @param {string} params.handoff - Handoff type (plan_to_code, code_to_review)
73
- * @param {Array} params.skillMatrix - Derived skill matrix
74
- * @param {Array} params.capabilities - All capabilities with checklists
75
- * @returns {Array<{capability: Object, level: string, items: string[]}>} Checklist items grouped by capability
76
- */
77
- export function deriveChecklist({ handoff, skillMatrix, capabilities }) {
78
48
  const result = [];
79
49
 
80
- for (const capability of capabilities) {
81
- // Skip if no checklists defined for this capability
82
- if (
83
- !capability.transitionChecklists ||
84
- !capability.transitionChecklists[handoff]
85
- ) {
50
+ for (const entry of skillMatrix) {
51
+ const skill = skillById.get(entry.skillId);
52
+ if (!skill || !skill.agent || !skill.agent.stages) {
86
53
  continue;
87
54
  }
88
55
 
89
- // Find the max skill level for this capability
90
- const maxLevel = getMaxCapabilityLevel(skillMatrix, capability.id);
91
-
92
- // Skip awareness level - not ready for checklists
93
- if (!maxLevel || maxLevel === "awareness") {
56
+ const stageData = skill.agent.stages[targetStage];
57
+ if (!stageData || !stageData.ready || stageData.ready.length === 0) {
94
58
  continue;
95
59
  }
96
60
 
97
- // Get all items up to the max level
98
- const items = getChecklistItemsUpToLevel(
99
- capability.transitionChecklists[handoff],
100
- maxLevel,
101
- );
102
-
103
- if (items.length > 0) {
104
- result.push({
105
- capability: {
106
- id: capability.id,
107
- name: capability.name,
108
- emoji: capability.emoji,
109
- },
110
- level: maxLevel,
111
- items,
112
- });
61
+ // Get capability for this skill
62
+ const capability = capabilityById.get(entry.capability);
63
+ if (!capability) {
64
+ continue;
113
65
  }
66
+
67
+ result.push({
68
+ skill: {
69
+ id: skill.id,
70
+ name: skill.name,
71
+ },
72
+ capability: {
73
+ id: capability.id,
74
+ name: capability.name,
75
+ emoji: capability.emoji,
76
+ },
77
+ items: stageData.ready,
78
+ });
114
79
  }
115
80
 
116
81
  return result;
@@ -118,8 +83,9 @@ export function deriveChecklist({ handoff, skillMatrix, capabilities }) {
118
83
 
119
84
  /**
120
85
  * Format a checklist for display (markdown format)
86
+ * Groups items by skill with capability emoji
121
87
  *
122
- * @param {Array<{capability: Object, level: string, items: string[]}>} checklist - Derived checklist
88
+ * @param {Array<{skill: Object, capability: Object, items: string[]}>} checklist - Derived checklist
123
89
  * @returns {string} Markdown-formatted checklist
124
90
  */
125
91
  export function formatChecklistMarkdown(checklist) {
@@ -127,8 +93,8 @@ export function formatChecklistMarkdown(checklist) {
127
93
  return "";
128
94
  }
129
95
 
130
- const sections = checklist.map(({ capability, items }) => {
131
- const header = `**${capability.emoji} ${capability.name}**`;
96
+ const sections = checklist.map(({ skill, capability, items }) => {
97
+ const header = `**${capability.emoji} ${skill.name}**`;
132
98
  const itemList = items.map((item) => `- [ ] ${item}`).join("\n");
133
99
  return `${header}\n\n${itemList}`;
134
100
  });