@lumenflow/core 1.0.0 → 1.3.2

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 (79) hide show
  1. package/dist/arg-parser.d.ts +6 -0
  2. package/dist/arg-parser.js +57 -1
  3. package/dist/backlog-generator.js +1 -1
  4. package/dist/backlog-sync-validator.js +3 -3
  5. package/dist/branch-check.d.ts +21 -0
  6. package/dist/branch-check.js +77 -0
  7. package/dist/cli/is-agent-branch.d.ts +11 -0
  8. package/dist/cli/is-agent-branch.js +15 -0
  9. package/dist/code-paths-overlap.js +2 -2
  10. package/dist/error-handler.d.ts +1 -0
  11. package/dist/error-handler.js +4 -1
  12. package/dist/git-adapter.d.ts +23 -0
  13. package/dist/git-adapter.js +38 -2
  14. package/dist/index.d.ts +3 -0
  15. package/dist/index.js +5 -0
  16. package/dist/lane-checker.d.ts +36 -3
  17. package/dist/lane-checker.js +128 -17
  18. package/dist/lane-inference.js +3 -4
  19. package/dist/lumenflow-config-schema.d.ts +125 -0
  20. package/dist/lumenflow-config-schema.js +76 -0
  21. package/dist/lumenflow-home.d.ts +130 -0
  22. package/dist/lumenflow-home.js +208 -0
  23. package/dist/manual-test-validator.js +1 -1
  24. package/dist/orchestration-rules.d.ts +1 -1
  25. package/dist/orchestration-rules.js +2 -2
  26. package/dist/orphan-detector.d.ts +16 -0
  27. package/dist/orphan-detector.js +24 -0
  28. package/dist/path-classifiers.d.ts +1 -1
  29. package/dist/path-classifiers.js +1 -1
  30. package/dist/rebase-artifact-cleanup.d.ts +17 -0
  31. package/dist/rebase-artifact-cleanup.js +49 -8
  32. package/dist/spawn-strategy.d.ts +53 -0
  33. package/dist/spawn-strategy.js +106 -0
  34. package/dist/spec-branch-helpers.d.ts +118 -0
  35. package/dist/spec-branch-helpers.js +192 -0
  36. package/dist/stamp-utils.d.ts +10 -0
  37. package/dist/stamp-utils.js +17 -19
  38. package/dist/token-counter.js +2 -2
  39. package/dist/wu-consistency-checker.d.ts +2 -0
  40. package/dist/wu-consistency-checker.js +40 -6
  41. package/dist/wu-constants.d.ts +98 -3
  42. package/dist/wu-constants.js +108 -3
  43. package/dist/wu-create-validators.d.ts +40 -2
  44. package/dist/wu-create-validators.js +76 -2
  45. package/dist/wu-done-branch-only.js +9 -0
  46. package/dist/wu-done-branch-utils.d.ts +10 -0
  47. package/dist/wu-done-branch-utils.js +31 -0
  48. package/dist/wu-done-cleanup.d.ts +8 -0
  49. package/dist/wu-done-cleanup.js +122 -0
  50. package/dist/wu-done-docs-generate.d.ts +73 -0
  51. package/dist/wu-done-docs-generate.js +108 -0
  52. package/dist/wu-done-docs-only.d.ts +20 -0
  53. package/dist/wu-done-docs-only.js +65 -0
  54. package/dist/wu-done-errors.d.ts +17 -0
  55. package/dist/wu-done-errors.js +24 -0
  56. package/dist/wu-done-inputs.d.ts +12 -0
  57. package/dist/wu-done-inputs.js +51 -0
  58. package/dist/wu-done-metadata.d.ts +100 -0
  59. package/dist/wu-done-metadata.js +193 -0
  60. package/dist/wu-done-paths.d.ts +69 -0
  61. package/dist/wu-done-paths.js +237 -0
  62. package/dist/wu-done-preflight.d.ts +48 -0
  63. package/dist/wu-done-preflight.js +185 -0
  64. package/dist/wu-done-validation.d.ts +82 -0
  65. package/dist/wu-done-validation.js +340 -0
  66. package/dist/wu-done-validators.d.ts +13 -409
  67. package/dist/wu-done-validators.js +9 -1225
  68. package/dist/wu-done-worktree.d.ts +0 -1
  69. package/dist/wu-done-worktree.js +24 -30
  70. package/dist/wu-schema.js +4 -4
  71. package/dist/wu-spawn-skills.d.ts +19 -0
  72. package/dist/wu-spawn-skills.js +148 -0
  73. package/dist/wu-spawn.d.ts +17 -4
  74. package/dist/wu-spawn.js +113 -177
  75. package/dist/wu-validation.d.ts +1 -0
  76. package/dist/wu-validation.js +21 -1
  77. package/dist/wu-validator.d.ts +51 -0
  78. package/dist/wu-validator.js +108 -0
  79. package/package.json +12 -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
  *
@@ -106,6 +89,9 @@ function formatAcceptance(acceptance) {
106
89
  /**
107
90
  * Format spec_refs as markdown links
108
91
  *
92
+ * WU-1062: Handles external paths (lumenflow://, ~/.lumenflow/, $LUMENFLOW_HOME/)
93
+ * by expanding them to absolute paths and adding a note about reading them.
94
+ *
109
95
  * @param {string[]|undefined} specRefs - Spec references array
110
96
  * @returns {string} Formatted references or empty string if none
111
97
  */
@@ -113,7 +99,17 @@ function formatSpecRefs(specRefs) {
113
99
  if (!specRefs || specRefs.length === 0) {
114
100
  return '';
115
101
  }
116
- return specRefs.map((ref) => `- ${ref}`).join('\n');
102
+ const formattedRefs = specRefs.map((ref) => {
103
+ // WU-1062: Add note for external paths
104
+ if (ref.startsWith('lumenflow://') ||
105
+ ref.startsWith('~/') ||
106
+ ref.startsWith('$LUMENFLOW_HOME') ||
107
+ (ref.startsWith('/') && ref.includes('.lumenflow'))) {
108
+ return `- ${ref} (external - read with filesystem access)`;
109
+ }
110
+ return `- ${ref}`;
111
+ });
112
+ return formattedRefs.join('\n');
117
113
  }
118
114
  /**
119
115
  * Format risks as markdown list
@@ -320,27 +316,15 @@ function generateTDDDirective() {
320
316
  * @param {string} id - WU ID
321
317
  * @returns {string} Context loading preamble
322
318
  */
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.`;
319
+ /**
320
+ * Generate the context loading preamble using the strategy
321
+ *
322
+ * @param {string} id - WU ID
323
+ * @param {import('./spawn-strategy.js').SpawnStrategy} strategy - Client strategy
324
+ * @returns {string} Context loading preamble
325
+ */
326
+ function generatePreamble(id, strategy) {
327
+ return strategy.getPreamble(id);
344
328
  }
345
329
  /**
346
330
  * Generate the constraints block (appended at end per Lost in the Middle research)
@@ -740,7 +724,7 @@ pnpm mem:triage --wu ${id} # List discoveries for this WU
740
724
  pnpm mem:triage --promote <node-id> --lane "<lane>" # Create Bug WU (human action)
741
725
  \`\`\`
742
726
 
743
- See: ai/onboarding/agent-invocation-guide.md §Bug Discovery`;
727
+ See: docs/04-operations/_frameworks/lumenflow/agent/onboarding/agent-invocation-guide.md §Bug Discovery`;
744
728
  }
745
729
  /**
746
730
  * Generate lane-specific guidance
@@ -809,128 +793,37 @@ Then implement following all standards above.
809
793
  - Lane lock acquisition (WIP=1 enforcement)
810
794
  - Session tracking for context recovery`;
811
795
  }
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
- `;
796
+ function generateClientBlocksSection(clientContext) {
797
+ if (!clientContext?.config?.blocks?.length)
798
+ return '';
799
+ const blocks = clientContext.config.blocks
800
+ .map((block) => `### ${block.title}\n\n${block.content}`)
801
+ .join('\n\n');
802
+ return `## Client Guidance (${clientContext.name})\n\n${blocks}`;
916
803
  }
917
804
  /**
918
805
  * Generate the complete Task tool invocation
919
806
  *
920
807
  * @param {object} doc - WU YAML document
921
808
  * @param {string} id - WU ID
809
+ * @param {SpawnStrategy} strategy - Client strategy
922
810
  * @param {object} [options={}] - Thinking mode options
923
811
  * @param {boolean} [options.thinking] - Whether extended thinking is enabled
924
812
  * @param {boolean} [options.noThinking] - Whether thinking is explicitly disabled
925
813
  * @param {string} [options.budget] - Token budget for thinking
926
814
  * @returns {string} Complete Task tool invocation
927
815
  */
928
- export function generateTaskInvocation(doc, id, options = {}) {
816
+ export function generateTaskInvocation(doc, id, strategy, options = {}) {
929
817
  const codePaths = doc.code_paths || [];
930
818
  const mandatoryAgents = detectMandatoryAgents(codePaths);
931
- const preamble = generatePreamble(id);
819
+ const preamble = generatePreamble(id, strategy);
932
820
  const tddDirective = generateTDDDirective();
933
- const skillsSection = generateSkillsSection(doc);
821
+ const clientContext = options.client;
822
+ const config = options.config || getConfig();
823
+ const clientSkillsGuidance = generateClientSkillsGuidance(clientContext);
824
+ const skillsSection = generateSkillsSelectionSection(doc, config, clientContext?.name) +
825
+ (clientSkillsGuidance ? `\n${clientSkillsGuidance}` : '');
826
+ const clientBlocks = generateClientBlocksSection(clientContext);
934
827
  const mandatorySection = generateMandatoryAgentSection(mandatoryAgents, id);
935
828
  const laneGuidance = generateLaneGuidance(doc.lane);
936
829
  const bugDiscoverySection = generateBugDiscoverySection(id);
@@ -1009,7 +902,7 @@ ${thinkingBlock}${skillsSection}
1009
902
  - **Documentation**: Update tooling docs if changing tools. Keep docs in sync with code
1010
903
  - **Sub-agents**: Use Explore agent for codebase investigation. Activate mandatory agents (security-auditor for PHI/auth, beacon-guardian for LLM/prompts)
1011
904
 
1012
- ${worktreeGuidance ? `---\n\n${worktreeGuidance}\n\n` : ''}---
905
+ ${clientBlocks ? `---\n\n${clientBlocks}\n\n` : ''}${worktreeGuidance ? `---\n\n${worktreeGuidance}\n\n` : ''}---
1013
906
 
1014
907
  ${bugDiscoverySection}
1015
908
 
@@ -1074,10 +967,10 @@ ${constraints}`;
1074
967
  ].join('\n');
1075
968
  return invocation;
1076
969
  }
1077
- export function generateCodexPrompt(doc, id, options = {}) {
970
+ export function generateCodexPrompt(doc, id, strategy, options = {}) {
1078
971
  const codePaths = doc.code_paths || [];
1079
972
  const mandatoryAgents = detectMandatoryAgents(codePaths);
1080
- const preamble = generatePreamble(id);
973
+ const preamble = generatePreamble(id, strategy);
1081
974
  const tddDirective = generateTDDDirective();
1082
975
  const mandatorySection = generateMandatoryAgentSection(mandatoryAgents, id);
1083
976
  const laneGuidance = generateLaneGuidance(doc.lane);
@@ -1085,6 +978,12 @@ export function generateCodexPrompt(doc, id, options = {}) {
1085
978
  const implementationContext = generateImplementationContext(doc);
1086
979
  const action = generateActionSection(doc, id);
1087
980
  const constraints = generateCodexConstraints(id);
981
+ const clientContext = options.client;
982
+ const config = options.config || getConfig();
983
+ const clientSkillsGuidance = generateClientSkillsGuidance(clientContext);
984
+ const skillsSection = generateSkillsSelectionSection(doc, config, clientContext?.name) +
985
+ (clientSkillsGuidance ? `\n${clientSkillsGuidance}` : '');
986
+ const clientBlocks = generateClientBlocksSection(clientContext);
1088
987
  const executionModeSection = generateExecutionModeSection(options);
1089
988
  const thinkToolGuidance = generateThinkToolGuidance(options);
1090
989
  const thinkingSections = [executionModeSection, thinkToolGuidance]
@@ -1127,6 +1026,10 @@ ${formatAcceptance(doc.acceptance)}
1127
1026
 
1128
1027
  ---
1129
1028
 
1029
+ ${skillsSection}
1030
+
1031
+ ---
1032
+
1130
1033
  ## Action
1131
1034
 
1132
1035
  ${action}
@@ -1140,7 +1043,7 @@ ${action}
1140
1043
 
1141
1044
  ---
1142
1045
 
1143
- ${mandatorySection}${implementationContext ? `${implementationContext}\n\n---\n\n` : ''}${thinkingBlock}${bugDiscoverySection}
1046
+ ${mandatorySection}${implementationContext ? `${implementationContext}\n\n---\n\n` : ''}${clientBlocks ? `${clientBlocks}\n\n---\n\n` : ''}${thinkingBlock}${bugDiscoverySection}
1144
1047
 
1145
1048
  ---
1146
1049
 
@@ -1196,12 +1099,19 @@ async function main() {
1196
1099
  name: 'wu-spawn',
1197
1100
  description: 'Generate Task tool invocation for sub-agent WU execution',
1198
1101
  options: [
1102
+ WU_OPTIONS.id,
1103
+ WU_OPTIONS.thinking,
1104
+ WU_OPTIONS.noThinking,
1105
+ WU_OPTIONS.budget,
1106
+ WU_OPTIONS.codex,
1199
1107
  WU_OPTIONS.id,
1200
1108
  WU_OPTIONS.thinking,
1201
1109
  WU_OPTIONS.noThinking,
1202
1110
  WU_OPTIONS.budget,
1203
1111
  WU_OPTIONS.codex,
1204
1112
  WU_OPTIONS.parentWu, // WU-1945: Parent WU for spawn registry tracking
1113
+ WU_OPTIONS.client,
1114
+ WU_OPTIONS.vendor,
1205
1115
  ],
1206
1116
  required: ['id'],
1207
1117
  allowPositionalId: true,
@@ -1274,33 +1184,59 @@ async function main() {
1274
1184
  noThinking: args.noThinking,
1275
1185
  budget: args.budget,
1276
1186
  };
1187
+ // Client Resolution
1188
+ const config = getConfig();
1189
+ let clientName = args.client;
1190
+ if (!clientName && args.vendor) {
1191
+ console.warn(`${LOG_PREFIX} ${EMOJI.WARNING} Warning: --vendor is deprecated. Use --client.`);
1192
+ clientName = args.vendor;
1193
+ }
1194
+ // Codex handling (deprecated legacy flag)
1277
1195
  if (args.codex) {
1278
- const prompt = generateCodexPrompt(doc, id, thinkingOptions);
1196
+ if (!clientName) {
1197
+ console.warn(`${LOG_PREFIX} ${EMOJI.WARNING} Warning: --codex is deprecated. Use --client codex-cli.`);
1198
+ clientName = 'codex-cli';
1199
+ }
1200
+ }
1201
+ if (!clientName) {
1202
+ clientName = config.agents.defaultClient || 'claude-code';
1203
+ }
1204
+ // Create strategy
1205
+ const strategy = SpawnStrategyFactory.create(clientName);
1206
+ const clientContext = { name: clientName, config: resolveClientConfig(config, clientName) };
1207
+ if (clientName === 'codex-cli' || args.codex) {
1208
+ const prompt = generateCodexPrompt(doc, id, strategy, {
1209
+ ...thinkingOptions,
1210
+ client: clientContext,
1211
+ config,
1212
+ });
1279
1213
  console.log(`${LOG_PREFIX} Generated Codex/GPT prompt for ${id}`);
1280
1214
  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',
1215
+ // ...
1216
+ // Generate and output the Task invocation
1217
+ const invocation = generateTaskInvocation(doc, id, strategy, {
1218
+ ...thinkingOptions,
1219
+ client: clientContext,
1220
+ config,
1297
1221
  });
1298
- const registryMessage = formatSpawnRecordedMessage(registryResult.spawnId, registryResult.error);
1299
- console.log(`\n${registryMessage}`);
1222
+ console.log(`${LOG_PREFIX} Generated Task tool invocation for ${id}`);
1223
+ console.log(`${LOG_PREFIX} Copy the block below to spawn a sub-agent:\n`);
1224
+ console.log(invocation);
1225
+ // WU-1945: Record spawn event to registry (non-blocking)
1226
+ // Only record if --parent-wu is provided (orchestrator context)
1227
+ if (args.parentWu) {
1228
+ const registryResult = await recordSpawnToRegistry({
1229
+ parentWuId: args.parentWu,
1230
+ targetWuId: id,
1231
+ lane: doc.lane || 'Unknown',
1232
+ baseDir: '.beacon/state',
1233
+ });
1234
+ const registryMessage = formatSpawnRecordedMessage(registryResult.spawnId, registryResult.error);
1235
+ console.log(`\n${registryMessage}`);
1236
+ }
1300
1237
  }
1301
1238
  }
1302
1239
  // Guard main() for testability
1303
- import { fileURLToPath } from 'node:url';
1304
1240
  if (process.argv[1] === fileURLToPath(import.meta.url)) {
1305
1241
  main();
1306
1242
  }
@@ -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
+ }