@lumenflow/core 1.0.0 → 1.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.
Files changed (65) hide show
  1. package/dist/arg-parser.js +31 -1
  2. package/dist/backlog-generator.js +1 -1
  3. package/dist/backlog-sync-validator.js +3 -3
  4. package/dist/branch-check.d.ts +21 -0
  5. package/dist/branch-check.js +77 -0
  6. package/dist/cli/is-agent-branch.d.ts +11 -0
  7. package/dist/cli/is-agent-branch.js +15 -0
  8. package/dist/code-paths-overlap.js +2 -2
  9. package/dist/error-handler.d.ts +1 -0
  10. package/dist/error-handler.js +4 -1
  11. package/dist/git-adapter.d.ts +16 -0
  12. package/dist/git-adapter.js +23 -1
  13. package/dist/index.d.ts +1 -0
  14. package/dist/index.js +2 -0
  15. package/dist/lane-checker.d.ts +36 -3
  16. package/dist/lane-checker.js +128 -17
  17. package/dist/lane-inference.js +3 -4
  18. package/dist/lumenflow-config-schema.d.ts +125 -0
  19. package/dist/lumenflow-config-schema.js +76 -0
  20. package/dist/orchestration-rules.d.ts +1 -1
  21. package/dist/orchestration-rules.js +2 -2
  22. package/dist/path-classifiers.d.ts +1 -1
  23. package/dist/path-classifiers.js +1 -1
  24. package/dist/rebase-artifact-cleanup.d.ts +17 -0
  25. package/dist/rebase-artifact-cleanup.js +49 -8
  26. package/dist/spawn-strategy.d.ts +53 -0
  27. package/dist/spawn-strategy.js +106 -0
  28. package/dist/stamp-utils.d.ts +10 -0
  29. package/dist/stamp-utils.js +17 -19
  30. package/dist/token-counter.js +2 -2
  31. package/dist/wu-consistency-checker.js +5 -5
  32. package/dist/wu-constants.d.ts +21 -3
  33. package/dist/wu-constants.js +28 -3
  34. package/dist/wu-done-branch-utils.d.ts +10 -0
  35. package/dist/wu-done-branch-utils.js +31 -0
  36. package/dist/wu-done-cleanup.d.ts +8 -0
  37. package/dist/wu-done-cleanup.js +122 -0
  38. package/dist/wu-done-docs-only.d.ts +20 -0
  39. package/dist/wu-done-docs-only.js +65 -0
  40. package/dist/wu-done-errors.d.ts +17 -0
  41. package/dist/wu-done-errors.js +24 -0
  42. package/dist/wu-done-inputs.d.ts +12 -0
  43. package/dist/wu-done-inputs.js +51 -0
  44. package/dist/wu-done-metadata.d.ts +100 -0
  45. package/dist/wu-done-metadata.js +193 -0
  46. package/dist/wu-done-paths.d.ts +69 -0
  47. package/dist/wu-done-paths.js +237 -0
  48. package/dist/wu-done-preflight.d.ts +48 -0
  49. package/dist/wu-done-preflight.js +185 -0
  50. package/dist/wu-done-validation.d.ts +82 -0
  51. package/dist/wu-done-validation.js +340 -0
  52. package/dist/wu-done-validators.d.ts +13 -409
  53. package/dist/wu-done-validators.js +9 -1225
  54. package/dist/wu-done-worktree.d.ts +0 -1
  55. package/dist/wu-done-worktree.js +12 -30
  56. package/dist/wu-schema.js +1 -3
  57. package/dist/wu-spawn-skills.d.ts +19 -0
  58. package/dist/wu-spawn-skills.js +148 -0
  59. package/dist/wu-spawn.d.ts +17 -4
  60. package/dist/wu-spawn.js +99 -176
  61. package/dist/wu-validation.d.ts +1 -0
  62. package/dist/wu-validation.js +21 -1
  63. package/dist/wu-validator.d.ts +51 -0
  64. package/dist/wu-validator.js +108 -0
  65. package/package.json +11 -8
package/dist/wu-spawn.js CHANGED
@@ -11,7 +11,7 @@
11
11
  *
12
12
  * Output:
13
13
  * A complete Task tool invocation block with:
14
- * - Context loading preamble (CLAUDE-core.md, README, lumenflow, WU YAML)
14
+ * - Context loading preamble (.claude/CLAUDE.md, README, lumenflow, WU YAML)
15
15
  * - WU details and acceptance criteria
16
16
  * - Skills Selection section (sub-agent reads catalogue and selects at runtime)
17
17
  * - Mandatory agent advisory
@@ -25,9 +25,10 @@
25
25
  * Codex Mode:
26
26
  * When --codex is used, outputs a Codex/GPT-friendly Markdown prompt (no antml/XML escaping).
27
27
  *
28
- * @see {@link ai/onboarding/agent-invocation-guide.md} - Context loading templates
28
+ * @see {@link docs/04-operations/_frameworks/lumenflow/agent/onboarding/agent-invocation-guide.md} - Context loading templates
29
29
  */
30
30
  import { existsSync, readFileSync } from 'node:fs';
31
+ import { fileURLToPath } from 'node:url';
31
32
  import path from 'node:path';
32
33
  import { createWUParser, WU_OPTIONS } from './arg-parser.js';
33
34
  import { WU_PATHS } from './wu-paths.js';
@@ -37,6 +38,9 @@ import { WU_STATUS, PATTERNS, EMOJI } from './wu-constants.js';
37
38
  // WU-1603: Check lane lock status before spawning
38
39
  import { checkLaneLock } from './lane-lock.js';
39
40
  import { minimatch } from 'minimatch';
41
+ import { SpawnStrategyFactory } from './spawn-strategy.js';
42
+ import { getConfig } from './lumenflow-config.js';
43
+ import { generateClientSkillsGuidance, generateSkillsSelectionSection, resolveClientConfig, } from './wu-spawn-skills.js';
40
44
  // WU-2252: Import invariants loader for spawn output injection
41
45
  import { loadInvariants, INVARIANT_TYPES } from './invariants-runner.js';
42
46
  import { validateSpawnArgs, generateExecutionModeSection, generateThinkToolGuidance, recordSpawnToRegistry, formatSpawnRecordedMessage, } from './wu-spawn-helpers.js';
@@ -51,27 +55,6 @@ const MANDATORY_TRIGGERS = {
51
55
  'beacon-guardian': ['**/prompts/**', '**/classification/**', '**/detector/**', '**/llm/**'],
52
56
  };
53
57
  const LOG_PREFIX = '[wu:spawn]';
54
- /** @type {string} */
55
- const AGENTS_DIR = '.claude/agents';
56
- /**
57
- * Load skills configured in agent's frontmatter
58
- *
59
- * @param {string} agentName - Agent name (e.g., 'general-purpose')
60
- * @returns {string[]} Array of skill names or empty array if not found
61
- */
62
- function loadAgentConfiguredSkills(agentName) {
63
- const agentPath = `${AGENTS_DIR}/${agentName}.md`;
64
- if (!existsSync(agentPath)) {
65
- return [];
66
- }
67
- try {
68
- const content = readFileSync(agentPath, { encoding: 'utf-8' });
69
- return []; // Removed: vendor-specific skill loading
70
- }
71
- catch {
72
- return [];
73
- }
74
- }
75
58
  /**
76
59
  * Detect mandatory agents based on code paths.
77
60
  *
@@ -320,27 +303,15 @@ function generateTDDDirective() {
320
303
  * @param {string} id - WU ID
321
304
  * @returns {string} Context loading preamble
322
305
  */
323
- function generatePreamble(id) {
324
- return `Load the following context in this order:
325
-
326
- 1. Read CLAUDE.md (workflow fundamentals and critical rules)
327
- 2. Read README.md (project structure and tech stack)
328
- 3. Read docs/04-operations/_frameworks/lumenflow/lumenflow-complete.md sections 1-7 (TDD, gates, Definition of Done)
329
- 4. Read docs/04-operations/tasks/wu/${id}.yaml (the specific WU you're working on)
330
-
331
- ## WIP=1 Lane Check (BEFORE claiming)
332
-
333
- Before running wu:claim, check docs/04-operations/tasks/status.md to ensure the lane is free.
334
- Only ONE WU can be in_progress per lane at any time.
335
-
336
- ## Context Recovery (Session Resumption)
337
-
338
- Before starting work, check for prior context from previous sessions:
339
-
340
- 1. \`pnpm mem:ready --wu ${id}\` — Query pending nodes (what's next?)
341
- 2. \`pnpm mem:inbox --wu ${id}\` — Check coordination signals from parallel agents
342
-
343
- If prior context exists, resume from the last checkpoint. Otherwise, proceed with the task below.`;
306
+ /**
307
+ * Generate the context loading preamble using the strategy
308
+ *
309
+ * @param {string} id - WU ID
310
+ * @param {import('./spawn-strategy.js').SpawnStrategy} strategy - Client strategy
311
+ * @returns {string} Context loading preamble
312
+ */
313
+ function generatePreamble(id, strategy) {
314
+ return strategy.getPreamble(id);
344
315
  }
345
316
  /**
346
317
  * Generate the constraints block (appended at end per Lost in the Middle research)
@@ -740,7 +711,7 @@ pnpm mem:triage --wu ${id} # List discoveries for this WU
740
711
  pnpm mem:triage --promote <node-id> --lane "<lane>" # Create Bug WU (human action)
741
712
  \`\`\`
742
713
 
743
- See: ai/onboarding/agent-invocation-guide.md §Bug Discovery`;
714
+ See: docs/04-operations/_frameworks/lumenflow/agent/onboarding/agent-invocation-guide.md §Bug Discovery`;
744
715
  }
745
716
  /**
746
717
  * Generate lane-specific guidance
@@ -809,128 +780,37 @@ Then implement following all standards above.
809
780
  - Lane lock acquisition (WIP=1 enforcement)
810
781
  - Session tracking for context recovery`;
811
782
  }
812
- /**
813
- * Generate the Skills Selection section for sub-agents.
814
- *
815
- * Unlike /wu-prompt (human-facing, skills selected at generation time),
816
- * wu:spawn instructs the sub-agent to read the catalogue and select skills
817
- * at execution time based on WU context.
818
- *
819
- * If an agentName is provided, that agent's configured skills (from frontmatter)
820
- * are auto-loaded at the top.
821
- *
822
- * @param {object} doc - WU YAML document
823
- * @param {string} [agentName='general-purpose'] - Agent to spawn
824
- * @returns {string} Skills Selection section
825
- */
826
- // eslint-disable-next-line sonarjs/cognitive-complexity -- WU-2025: Pre-existing complexity, refactor tracked
827
- function generateSkillsSection(doc, agentName = 'general-purpose') {
828
- const lane = doc.lane || '';
829
- const type = doc.type || 'feature';
830
- const laneParent = lane.split(':')[0].trim();
831
- // Load agent's configured skills from frontmatter
832
- const agentSkills = loadAgentConfiguredSkills(agentName);
833
- const hasAgentSkills = agentSkills.length > 0;
834
- // Build auto-load section if agent has configured skills
835
- const autoLoadSection = hasAgentSkills
836
- ? `### Auto-Loaded Skills (from ${agentName} agent config)
837
-
838
- These skills are pre-configured for this agent and should be loaded first:
839
-
840
- ${agentSkills.map((s) => `- \`${s}\` — Load via \`/skill ${s}\``).join('\n')}
841
-
842
- `
843
- : '';
844
- // Build context hints for the sub-agent
845
- const contextHints = [];
846
- // Universal baselines (only if not already in agent skills)
847
- if (!agentSkills.includes('wu-lifecycle')) {
848
- contextHints.push('- `wu-lifecycle` — ALL WUs need workflow automation');
849
- }
850
- if (!agentSkills.includes('worktree-discipline')) {
851
- contextHints.push('- `worktree-discipline` — ALL WUs need path safety');
852
- }
853
- // Type-based hints
854
- if ((type === 'feature' || type === 'enhancement') && !agentSkills.includes('tdd-workflow')) {
855
- contextHints.push('- `tdd-workflow` — TDD is mandatory for feature/enhancement WUs');
856
- }
857
- if (type === 'bug' && !agentSkills.includes('bug-classification')) {
858
- contextHints.push('- `bug-classification` — Bug severity assessment');
859
- }
860
- // Lane-based hints
861
- if (laneParent === 'Operations' &&
862
- lane.includes('Tooling') &&
863
- !agentSkills.includes('lumenflow-gates')) {
864
- contextHints.push('- `lumenflow-gates` — Tooling often affects gates');
865
- }
866
- if (laneParent === 'Intelligence') {
867
- if (!agentSkills.includes('beacon-compliance')) {
868
- contextHints.push('- `beacon-compliance` — Intelligence lane requires Beacon validation');
869
- }
870
- if (!agentSkills.includes('prompt-management')) {
871
- contextHints.push('- `prompt-management` — For prompt template work');
872
- }
873
- }
874
- if (laneParent === 'Experience' && !agentSkills.includes('frontend-design')) {
875
- contextHints.push('- `frontend-design` — For UI component work');
876
- }
877
- const softPolicySection = contextHints.length > 0
878
- ? `### Soft Policy (baselines for this WU)
879
-
880
- Based on WU context, consider loading:
881
-
882
- ${contextHints.join('\n')}
883
-
884
- `
885
- : '';
886
- return `## Skills Selection
887
-
888
- **IMPORTANT**: Before starting work, select and load relevant skills.
889
-
890
- ${autoLoadSection}### How to Select Skills
891
-
892
- 1. Read the skill catalogue frontmatter from \`.claude/skills/*/SKILL.md\`
893
- 2. Match skills to WU context (lane, type, code_paths, description)
894
- 3. Load selected skills via \`/skill <skill-name>\`
895
-
896
- ${softPolicySection}### Additional Skills (load if needed)
897
-
898
- | Skill | Use When |
899
- |-------|----------|
900
- | lumenflow-gates | Gates fail, debugging format/lint/typecheck errors |
901
- | bug-classification | Bug discovered mid-WU, need priority classification |
902
- | beacon-compliance | Code touches LLM, prompts, classification |
903
- | prompt-management | Working with prompt templates, golden datasets |
904
- | frontend-design | Building UI components, pages |
905
- | initiative-management | Multi-phase projects, INIT-XXX coordination |
906
- | multi-agent-coordination | Spawning sub-agents, parallel WU work |
907
- | orchestration | Agent coordination, mandatory agent checks |
908
- | ops-maintenance | Metrics, validation, health checks |
909
-
910
- ### Graceful Degradation
911
-
912
- If the skill catalogue is missing or invalid:
913
- - Load baseline skills: \`/skill wu-lifecycle\`, \`/skill tdd-workflow\` (for features)
914
- - Continue with implementation using Mandatory Standards below
915
- `;
783
+ function generateClientBlocksSection(clientContext) {
784
+ if (!clientContext?.config?.blocks?.length)
785
+ return '';
786
+ const blocks = clientContext.config.blocks
787
+ .map((block) => `### ${block.title}\n\n${block.content}`)
788
+ .join('\n\n');
789
+ return `## Client Guidance (${clientContext.name})\n\n${blocks}`;
916
790
  }
917
791
  /**
918
792
  * Generate the complete Task tool invocation
919
793
  *
920
794
  * @param {object} doc - WU YAML document
921
795
  * @param {string} id - WU ID
796
+ * @param {SpawnStrategy} strategy - Client strategy
922
797
  * @param {object} [options={}] - Thinking mode options
923
798
  * @param {boolean} [options.thinking] - Whether extended thinking is enabled
924
799
  * @param {boolean} [options.noThinking] - Whether thinking is explicitly disabled
925
800
  * @param {string} [options.budget] - Token budget for thinking
926
801
  * @returns {string} Complete Task tool invocation
927
802
  */
928
- export function generateTaskInvocation(doc, id, options = {}) {
803
+ export function generateTaskInvocation(doc, id, strategy, options = {}) {
929
804
  const codePaths = doc.code_paths || [];
930
805
  const mandatoryAgents = detectMandatoryAgents(codePaths);
931
- const preamble = generatePreamble(id);
806
+ const preamble = generatePreamble(id, strategy);
932
807
  const tddDirective = generateTDDDirective();
933
- const skillsSection = generateSkillsSection(doc);
808
+ const clientContext = options.client;
809
+ const config = options.config || getConfig();
810
+ const clientSkillsGuidance = generateClientSkillsGuidance(clientContext);
811
+ const skillsSection = generateSkillsSelectionSection(doc, config, clientContext?.name) +
812
+ (clientSkillsGuidance ? `\n${clientSkillsGuidance}` : '');
813
+ const clientBlocks = generateClientBlocksSection(clientContext);
934
814
  const mandatorySection = generateMandatoryAgentSection(mandatoryAgents, id);
935
815
  const laneGuidance = generateLaneGuidance(doc.lane);
936
816
  const bugDiscoverySection = generateBugDiscoverySection(id);
@@ -1009,7 +889,7 @@ ${thinkingBlock}${skillsSection}
1009
889
  - **Documentation**: Update tooling docs if changing tools. Keep docs in sync with code
1010
890
  - **Sub-agents**: Use Explore agent for codebase investigation. Activate mandatory agents (security-auditor for PHI/auth, beacon-guardian for LLM/prompts)
1011
891
 
1012
- ${worktreeGuidance ? `---\n\n${worktreeGuidance}\n\n` : ''}---
892
+ ${clientBlocks ? `---\n\n${clientBlocks}\n\n` : ''}${worktreeGuidance ? `---\n\n${worktreeGuidance}\n\n` : ''}---
1013
893
 
1014
894
  ${bugDiscoverySection}
1015
895
 
@@ -1074,10 +954,10 @@ ${constraints}`;
1074
954
  ].join('\n');
1075
955
  return invocation;
1076
956
  }
1077
- export function generateCodexPrompt(doc, id, options = {}) {
957
+ export function generateCodexPrompt(doc, id, strategy, options = {}) {
1078
958
  const codePaths = doc.code_paths || [];
1079
959
  const mandatoryAgents = detectMandatoryAgents(codePaths);
1080
- const preamble = generatePreamble(id);
960
+ const preamble = generatePreamble(id, strategy);
1081
961
  const tddDirective = generateTDDDirective();
1082
962
  const mandatorySection = generateMandatoryAgentSection(mandatoryAgents, id);
1083
963
  const laneGuidance = generateLaneGuidance(doc.lane);
@@ -1085,6 +965,12 @@ export function generateCodexPrompt(doc, id, options = {}) {
1085
965
  const implementationContext = generateImplementationContext(doc);
1086
966
  const action = generateActionSection(doc, id);
1087
967
  const constraints = generateCodexConstraints(id);
968
+ const clientContext = options.client;
969
+ const config = options.config || getConfig();
970
+ const clientSkillsGuidance = generateClientSkillsGuidance(clientContext);
971
+ const skillsSection = generateSkillsSelectionSection(doc, config, clientContext?.name) +
972
+ (clientSkillsGuidance ? `\n${clientSkillsGuidance}` : '');
973
+ const clientBlocks = generateClientBlocksSection(clientContext);
1088
974
  const executionModeSection = generateExecutionModeSection(options);
1089
975
  const thinkToolGuidance = generateThinkToolGuidance(options);
1090
976
  const thinkingSections = [executionModeSection, thinkToolGuidance]
@@ -1127,6 +1013,10 @@ ${formatAcceptance(doc.acceptance)}
1127
1013
 
1128
1014
  ---
1129
1015
 
1016
+ ${skillsSection}
1017
+
1018
+ ---
1019
+
1130
1020
  ## Action
1131
1021
 
1132
1022
  ${action}
@@ -1140,7 +1030,7 @@ ${action}
1140
1030
 
1141
1031
  ---
1142
1032
 
1143
- ${mandatorySection}${implementationContext ? `${implementationContext}\n\n---\n\n` : ''}${thinkingBlock}${bugDiscoverySection}
1033
+ ${mandatorySection}${implementationContext ? `${implementationContext}\n\n---\n\n` : ''}${clientBlocks ? `${clientBlocks}\n\n---\n\n` : ''}${thinkingBlock}${bugDiscoverySection}
1144
1034
 
1145
1035
  ---
1146
1036
 
@@ -1196,12 +1086,19 @@ async function main() {
1196
1086
  name: 'wu-spawn',
1197
1087
  description: 'Generate Task tool invocation for sub-agent WU execution',
1198
1088
  options: [
1089
+ WU_OPTIONS.id,
1090
+ WU_OPTIONS.thinking,
1091
+ WU_OPTIONS.noThinking,
1092
+ WU_OPTIONS.budget,
1093
+ WU_OPTIONS.codex,
1199
1094
  WU_OPTIONS.id,
1200
1095
  WU_OPTIONS.thinking,
1201
1096
  WU_OPTIONS.noThinking,
1202
1097
  WU_OPTIONS.budget,
1203
1098
  WU_OPTIONS.codex,
1204
1099
  WU_OPTIONS.parentWu, // WU-1945: Parent WU for spawn registry tracking
1100
+ WU_OPTIONS.client,
1101
+ WU_OPTIONS.vendor,
1205
1102
  ],
1206
1103
  required: ['id'],
1207
1104
  allowPositionalId: true,
@@ -1274,33 +1171,59 @@ async function main() {
1274
1171
  noThinking: args.noThinking,
1275
1172
  budget: args.budget,
1276
1173
  };
1174
+ // Client Resolution
1175
+ const config = getConfig();
1176
+ let clientName = args.client;
1177
+ if (!clientName && args.vendor) {
1178
+ console.warn(`${LOG_PREFIX} ${EMOJI.WARNING} Warning: --vendor is deprecated. Use --client.`);
1179
+ clientName = args.vendor;
1180
+ }
1181
+ // Codex handling (deprecated legacy flag)
1277
1182
  if (args.codex) {
1278
- const prompt = generateCodexPrompt(doc, id, thinkingOptions);
1183
+ if (!clientName) {
1184
+ console.warn(`${LOG_PREFIX} ${EMOJI.WARNING} Warning: --codex is deprecated. Use --client codex-cli.`);
1185
+ clientName = 'codex-cli';
1186
+ }
1187
+ }
1188
+ if (!clientName) {
1189
+ clientName = config.agents.defaultClient || 'claude-code';
1190
+ }
1191
+ // Create strategy
1192
+ const strategy = SpawnStrategyFactory.create(clientName);
1193
+ const clientContext = { name: clientName, config: resolveClientConfig(config, clientName) };
1194
+ if (clientName === 'codex-cli' || args.codex) {
1195
+ const prompt = generateCodexPrompt(doc, id, strategy, {
1196
+ ...thinkingOptions,
1197
+ client: clientContext,
1198
+ config,
1199
+ });
1279
1200
  console.log(`${LOG_PREFIX} Generated Codex/GPT prompt for ${id}`);
1280
1201
  console.log(`${LOG_PREFIX} Copy the Markdown below:\n`);
1281
- console.log(prompt.trimEnd());
1282
- return;
1283
- }
1284
- // Generate and output the Task invocation
1285
- const invocation = generateTaskInvocation(doc, id, thinkingOptions);
1286
- console.log(`${LOG_PREFIX} Generated Task tool invocation for ${id}`);
1287
- console.log(`${LOG_PREFIX} Copy the block below to spawn a sub-agent:\n`);
1288
- console.log(invocation);
1289
- // WU-1945: Record spawn event to registry (non-blocking)
1290
- // Only record if --parent-wu is provided (orchestrator context)
1291
- if (args.parentWu) {
1292
- const registryResult = await recordSpawnToRegistry({
1293
- parentWuId: args.parentWu,
1294
- targetWuId: id,
1295
- lane: doc.lane || 'Unknown',
1296
- baseDir: '.beacon/state',
1202
+ // ...
1203
+ // Generate and output the Task invocation
1204
+ const invocation = generateTaskInvocation(doc, id, strategy, {
1205
+ ...thinkingOptions,
1206
+ client: clientContext,
1207
+ config,
1297
1208
  });
1298
- const registryMessage = formatSpawnRecordedMessage(registryResult.spawnId, registryResult.error);
1299
- console.log(`\n${registryMessage}`);
1209
+ console.log(`${LOG_PREFIX} Generated Task tool invocation for ${id}`);
1210
+ console.log(`${LOG_PREFIX} Copy the block below to spawn a sub-agent:\n`);
1211
+ console.log(invocation);
1212
+ // WU-1945: Record spawn event to registry (non-blocking)
1213
+ // Only record if --parent-wu is provided (orchestrator context)
1214
+ if (args.parentWu) {
1215
+ const registryResult = await recordSpawnToRegistry({
1216
+ parentWuId: args.parentWu,
1217
+ targetWuId: id,
1218
+ lane: doc.lane || 'Unknown',
1219
+ baseDir: '.beacon/state',
1220
+ });
1221
+ const registryMessage = formatSpawnRecordedMessage(registryResult.spawnId, registryResult.error);
1222
+ console.log(`\n${registryMessage}`);
1223
+ }
1300
1224
  }
1301
1225
  }
1302
1226
  // Guard main() for testability
1303
- import { fileURLToPath } from 'node:url';
1304
1227
  if (process.argv[1] === fileURLToPath(import.meta.url)) {
1305
1228
  main();
1306
1229
  }
@@ -13,6 +13,7 @@
13
13
  * @see {@link tools/lib/wu-schema.mjs} - WU schema with exposure field
14
14
  * @see {@link tools/lib/wu-constants.mjs} - WU_EXPOSURE values
15
15
  */
16
+ export declare function resolveExposureDefault(lane: any): "documentation" | "backend-only";
16
17
  /**
17
18
  * Warning message templates with remediation guidance.
18
19
  * All messages include the WU ID for context.
@@ -14,6 +14,26 @@
14
14
  * @see {@link tools/lib/wu-constants.mjs} - WU_EXPOSURE values
15
15
  */
16
16
  import { WU_EXPOSURE } from './wu-constants.js';
17
+ const LANE_PARENTS = {
18
+ CONTENT: 'content',
19
+ FRAMEWORK: 'framework',
20
+ OPERATIONS: 'operations',
21
+ };
22
+ function getLaneParent(lane) {
23
+ if (!lane || typeof lane !== 'string')
24
+ return '';
25
+ return lane.split(':')[0].trim().toLowerCase();
26
+ }
27
+ export function resolveExposureDefault(lane) {
28
+ const parent = getLaneParent(lane);
29
+ if (parent === LANE_PARENTS.CONTENT) {
30
+ return WU_EXPOSURE.DOCUMENTATION;
31
+ }
32
+ if (parent === LANE_PARENTS.FRAMEWORK || parent === LANE_PARENTS.OPERATIONS) {
33
+ return WU_EXPOSURE.BACKEND_ONLY;
34
+ }
35
+ return undefined;
36
+ }
17
37
  /**
18
38
  * UI verification keywords to search for in acceptance criteria.
19
39
  * Case-insensitive patterns that indicate the acceptance criteria
@@ -99,7 +119,7 @@ export function validateExposure(wu, options = {}) {
99
119
  return { valid: true, warnings: [] };
100
120
  }
101
121
  const wuId = wu.id || 'WU-???';
102
- const exposure = wu.exposure;
122
+ const exposure = wu.exposure || resolveExposureDefault(wu.lane);
103
123
  // Check 1: exposure field presence
104
124
  if (!exposure) {
105
125
  warnings.push(EXPOSURE_WARNING_MESSAGES.MISSING_EXPOSURE(wuId));
@@ -60,3 +60,54 @@ export declare function validateWUCodePaths(codePaths: any, options?: ValidateWU
60
60
  errors: any[];
61
61
  warnings: any[];
62
62
  };
63
+ /**
64
+ * Result of placeholder validation
65
+ */
66
+ export interface PlaceholderValidationResult {
67
+ /** Whether validation passed (no placeholders found) */
68
+ valid: boolean;
69
+ /** List of validation errors */
70
+ errors: string[];
71
+ /** Fields that contain placeholders */
72
+ fieldsWithPlaceholders: string[];
73
+ }
74
+ /**
75
+ * WU-1025: Validate that WU spec content does not contain PLACEHOLDER markers
76
+ *
77
+ * Used by wu:create (for inline content) and wu:claim (for full spec).
78
+ * Provides clear error messages telling the user which fields need to be fixed.
79
+ *
80
+ * @param {object} spec - WU spec content to validate
81
+ * @param {string} [spec.description] - WU description
82
+ * @param {string[]|object} [spec.acceptance] - Acceptance criteria (array or object)
83
+ * @returns {PlaceholderValidationResult} Validation result with errors and affected fields
84
+ *
85
+ * @example
86
+ * // wu:create validation (inline content only)
87
+ * const result = validateNoPlaceholders({ description: args.description });
88
+ * if (!result.valid) {
89
+ * die(`Cannot create WU:\n${result.errors.join('\n')}`);
90
+ * }
91
+ *
92
+ * @example
93
+ * // wu:claim validation (full spec)
94
+ * const result = validateNoPlaceholders(wuDoc);
95
+ * if (!result.valid) {
96
+ * die(`Cannot claim WU:\n${result.errors.join('\n')}`);
97
+ * }
98
+ */
99
+ export declare function validateNoPlaceholders(spec: {
100
+ description?: string;
101
+ acceptance?: string[] | Record<string, string[]>;
102
+ }): PlaceholderValidationResult;
103
+ /**
104
+ * WU-1025: Build error message for placeholder validation failure
105
+ *
106
+ * Creates a user-friendly error message with actionable guidance.
107
+ *
108
+ * @param {string} command - Command that failed ('wu:create' or 'wu:claim')
109
+ * @param {PlaceholderValidationResult} result - Validation result
110
+ * @param {string} [wuId] - WU ID (for wu:claim error messages)
111
+ * @returns {string} Formatted error message
112
+ */
113
+ export declare function buildPlaceholderErrorMessage(command: string, result: PlaceholderValidationResult, wuId?: string): string;
@@ -14,6 +14,7 @@ import { readFileSync, existsSync } from 'node:fs';
14
14
  import path from 'node:path';
15
15
  import { execSync } from 'node:child_process';
16
16
  import { STDIO } from './wu-constants.js';
17
+ import { PLACEHOLDER_SENTINEL } from './wu-schema.js';
17
18
  /**
18
19
  * Check if a file path is a test file
19
20
  * @param {string} filePath - Path to check
@@ -323,3 +324,110 @@ function formatMockFindings(findings) {
323
324
  msg += '\nVerify these are actual implementations, not placeholder code.';
324
325
  return msg;
325
326
  }
327
+ /**
328
+ * WU-1025: Validate that WU spec content does not contain PLACEHOLDER markers
329
+ *
330
+ * Used by wu:create (for inline content) and wu:claim (for full spec).
331
+ * Provides clear error messages telling the user which fields need to be fixed.
332
+ *
333
+ * @param {object} spec - WU spec content to validate
334
+ * @param {string} [spec.description] - WU description
335
+ * @param {string[]|object} [spec.acceptance] - Acceptance criteria (array or object)
336
+ * @returns {PlaceholderValidationResult} Validation result with errors and affected fields
337
+ *
338
+ * @example
339
+ * // wu:create validation (inline content only)
340
+ * const result = validateNoPlaceholders({ description: args.description });
341
+ * if (!result.valid) {
342
+ * die(`Cannot create WU:\n${result.errors.join('\n')}`);
343
+ * }
344
+ *
345
+ * @example
346
+ * // wu:claim validation (full spec)
347
+ * const result = validateNoPlaceholders(wuDoc);
348
+ * if (!result.valid) {
349
+ * die(`Cannot claim WU:\n${result.errors.join('\n')}`);
350
+ * }
351
+ */
352
+ export function validateNoPlaceholders(spec) {
353
+ const errors = [];
354
+ const fieldsWithPlaceholders = [];
355
+ // Check description
356
+ if (spec.description && spec.description.includes(PLACEHOLDER_SENTINEL)) {
357
+ fieldsWithPlaceholders.push('description');
358
+ errors.push(`Description contains ${PLACEHOLDER_SENTINEL} marker.\n` +
359
+ ` Fix: Replace placeholder text with actual description.\n` +
360
+ ` Example: --description "Implement X feature to enable Y functionality"`);
361
+ }
362
+ // Check acceptance criteria (supports both array and object formats)
363
+ if (spec.acceptance) {
364
+ const hasPlaceholder = checkForPlaceholderInAcceptance(spec.acceptance);
365
+ if (hasPlaceholder) {
366
+ fieldsWithPlaceholders.push('acceptance');
367
+ errors.push(`Acceptance criteria contain ${PLACEHOLDER_SENTINEL} markers.\n` +
368
+ ` Fix: Replace placeholder text with actual acceptance criteria.\n` +
369
+ ` Example: --acceptance "Feature X works as expected" --acceptance "Tests pass"`);
370
+ }
371
+ }
372
+ return {
373
+ valid: errors.length === 0,
374
+ errors,
375
+ fieldsWithPlaceholders,
376
+ };
377
+ }
378
+ /**
379
+ * Helper: Recursively check acceptance criteria for placeholder markers
380
+ *
381
+ * Supports both formats:
382
+ * - Flat array: ["criterion 1", "criterion 2"]
383
+ * - Nested object: { functional: ["criterion"], technical: ["criterion"] }
384
+ *
385
+ * @param {string[]|object} acceptance - Acceptance criteria
386
+ * @returns {boolean} True if any placeholder found
387
+ */
388
+ function checkForPlaceholderInAcceptance(acceptance) {
389
+ if (Array.isArray(acceptance)) {
390
+ return acceptance.some((item) => typeof item === 'string' && item.includes(PLACEHOLDER_SENTINEL));
391
+ }
392
+ if (typeof acceptance === 'object' && acceptance !== null) {
393
+ return Object.values(acceptance).some((value) => {
394
+ if (Array.isArray(value)) {
395
+ return value.some((item) => typeof item === 'string' && item.includes(PLACEHOLDER_SENTINEL));
396
+ }
397
+ return false;
398
+ });
399
+ }
400
+ return false;
401
+ }
402
+ /**
403
+ * WU-1025: Build error message for placeholder validation failure
404
+ *
405
+ * Creates a user-friendly error message with actionable guidance.
406
+ *
407
+ * @param {string} command - Command that failed ('wu:create' or 'wu:claim')
408
+ * @param {PlaceholderValidationResult} result - Validation result
409
+ * @param {string} [wuId] - WU ID (for wu:claim error messages)
410
+ * @returns {string} Formatted error message
411
+ */
412
+ export function buildPlaceholderErrorMessage(command, result, wuId) {
413
+ const header = command === 'wu:create'
414
+ ? `Cannot create WU with placeholder markers`
415
+ : `Cannot claim ${wuId || 'WU'} - spec contains placeholder markers`;
416
+ const fieldsText = result.fieldsWithPlaceholders.join(', ');
417
+ let message = `
418
+ ❌ ${header}
419
+
420
+ Fields with ${PLACEHOLDER_SENTINEL} markers: ${fieldsText}
421
+
422
+ ${result.errors.join('\n\n')}
423
+ `;
424
+ if (command === 'wu:claim' && wuId) {
425
+ message += `
426
+ To fix, edit the WU spec:
427
+ pnpm wu:edit --id ${wuId} --description "..." --acceptance "..."
428
+
429
+ Or manually edit: docs/04-operations/tasks/wu/${wuId}.yaml
430
+ `;
431
+ }
432
+ return message;
433
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumenflow/core",
3
- "version": "1.0.0",
3
+ "version": "1.3.0",
4
4
  "description": "Core WU lifecycle tools for LumenFlow workflow framework",
5
5
  "keywords": [
6
6
  "lumenflow",
@@ -57,6 +57,9 @@
57
57
  "./lib/*": "./dist/*"
58
58
  },
59
59
  "main": "./dist/index.js",
60
+ "bin": {
61
+ "is-agent-branch": "./dist/cli/is-agent-branch.js"
62
+ },
60
63
  "files": [
61
64
  "dist",
62
65
  "LICENSE",
@@ -66,12 +69,11 @@
66
69
  "change-case": "^5.4.4",
67
70
  "cli-progress": "^3.12.0",
68
71
  "cli-table3": "^0.6.5",
69
- "commander": "^12.1.0",
72
+ "commander": "^14.0.2",
70
73
  "date-fns": "^4.1.0",
71
74
  "fast-glob": "^3.3.3",
72
75
  "glob": "^11.0.0",
73
76
  "gray-matter": "^4.0.3",
74
- "js-yaml": "^4.1.0",
75
77
  "micromatch": "^4.0.8",
76
78
  "minimatch": "^10.1.1",
77
79
  "picocolors": "^1.1.1",
@@ -79,16 +81,17 @@
79
81
  "ps-list": "^8.1.1",
80
82
  "simple-git": "^3.30.0",
81
83
  "tiktoken": "^1.0.21",
82
- "yaml": "^2.7.0",
84
+ "yaml": "^2.8.2",
83
85
  "zod": "^4.3.5"
84
86
  },
85
87
  "devDependencies": {
86
- "typescript": "^5.7.0",
87
- "vitest": "^2.1.0"
88
+ "@vitest/coverage-v8": "^4.0.17",
89
+ "typescript": "^5.9.3",
90
+ "vitest": "^4.0.17"
88
91
  },
89
92
  "peerDependencies": {
90
- "@lumenflow/memory": "1.0.0",
91
- "@lumenflow/initiatives": "1.0.0"
93
+ "@lumenflow/memory": "1.3.0",
94
+ "@lumenflow/initiatives": "1.3.0"
92
95
  },
93
96
  "peerDependenciesMeta": {
94
97
  "@lumenflow/memory": {