create-byan-agent 2.19.2 → 2.20.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 (49) hide show
  1. package/CHANGELOG.md +148 -0
  2. package/README.md +4 -4
  3. package/install/src/byan-v2/generation/templates/default-agent.md +1 -1
  4. package/install/templates/.claude/CLAUDE.md +1 -1
  5. package/install/templates/.claude/hooks/fd-phase-guard.js +2 -2
  6. package/install/templates/.claude/hooks/mantra-validate.js +16 -8
  7. package/install/templates/.claude/hooks/strict-scope-guard.js +25 -7
  8. package/install/templates/.claude/rules/native-workflows.md +32 -0
  9. package/install/templates/.claude/skills/byan-byan/SKILL.md +5 -5
  10. package/install/templates/.claude/skills/byan-mantra-audit/SKILL.md +53 -0
  11. package/install/templates/.claude/skills/byan-merise-agile/SKILL.md +2 -2
  12. package/install/templates/.claude/skills/byan-native-dev-story/SKILL.md +83 -0
  13. package/install/templates/.claude/workflows/INDEX.md +35 -0
  14. package/install/templates/.claude/workflows/check-implementation-readiness.js +280 -0
  15. package/install/templates/.claude/workflows/code-review.js +179 -0
  16. package/install/templates/.claude/workflows/create-excalidraw-dataflow.js +214 -0
  17. package/install/templates/.claude/workflows/create-excalidraw-diagram.js +188 -0
  18. package/install/templates/.claude/workflows/create-excalidraw-flowchart.js +225 -0
  19. package/install/templates/.claude/workflows/create-excalidraw-wireframe.js +192 -0
  20. package/install/templates/.claude/workflows/create-story.js +216 -0
  21. package/install/templates/.claude/workflows/dev-story.js +100 -0
  22. package/install/templates/.claude/workflows/document-project.js +455 -0
  23. package/install/templates/.claude/workflows/qa-automate.js +169 -0
  24. package/install/templates/.claude/workflows/quick-dev.js +273 -0
  25. package/install/templates/.claude/workflows/sprint-planning.js +261 -0
  26. package/install/templates/.claude/workflows/testarch-atdd.js +287 -0
  27. package/install/templates/.claude/workflows/testarch-automate.js +229 -0
  28. package/install/templates/.claude/workflows/testarch-ci.js +184 -0
  29. package/install/templates/.claude/workflows/testarch-framework.js +267 -0
  30. package/install/templates/.claude/workflows/testarch-nfr.js +316 -0
  31. package/install/templates/.claude/workflows/testarch-test-design.js +293 -0
  32. package/install/templates/.claude/workflows/testarch-test-review.js +321 -0
  33. package/install/templates/.claude/workflows/testarch-trace.js +316 -0
  34. package/install/templates/.githooks/pre-commit +49 -15
  35. package/install/templates/_byan/config.yaml +15 -5
  36. package/install/templates/_byan/mcp/byan-mcp-server/bin/byan-build-workflows.js +20 -0
  37. package/install/templates/_byan/mcp/byan-mcp-server/bin/byan-lint-workflows.js +57 -0
  38. package/install/templates/_byan/mcp/byan-mcp-server/lib/native-loop.js +39 -0
  39. package/install/templates/_byan/mcp/byan-mcp-server/lib/workflows-generator.js +149 -0
  40. package/install/templates/_byan/mcp/byan-mcp-server/lib/workflows-lint.js +113 -0
  41. package/install/templates/_byan/workflow/simple/byan/feature-workflow.md +14 -11
  42. package/install/templates/docs/native-workflows-contract.md +84 -0
  43. package/package.json +2 -2
  44. package/src/byan-v2/data/agent-scopes.json +46 -0
  45. package/src/byan-v2/data/mantras.json +194 -8
  46. package/src/byan-v2/generation/mantra-audit.js +147 -0
  47. package/src/byan-v2/generation/mantra-validator.js +56 -6
  48. package/src/byan-v2/generation/scope-resolver.js +102 -0
  49. package/src/byan-v2/generation/templates/default-agent.md +1 -1
@@ -0,0 +1,273 @@
1
+ export const meta = {
2
+ name: 'quick-dev',
3
+ description: 'Native port of the BYAN quick-dev workflow: flexible implementation from a tech-spec OR direct instructions, with self-check, an adversarial diff review, and a structured findings verdict for the orchestrating skill to present at the human gate.',
4
+ phases: [
5
+ { title: 'MODE', detail: 'capture baseline + classify tech-spec vs direct + escalation signals (no human menu here)' },
6
+ { title: 'CONTEXT', detail: 'direct mode only: identify files, patterns, dependencies, infer AC + plan' },
7
+ { title: 'EXECUTE', detail: 'implement all tasks task-by-task; per-task convergence cap mirrors the 3-failure HALT rule' },
8
+ { title: 'SELF_CHECK', detail: 'audit tasks/tests/AC/patterns; update tech-spec status (mode A)' },
9
+ { title: 'ADVERSARIAL_REVIEW', detail: 'build the baseline diff, run adversarial review, order + number findings (zero findings is suspicious)' },
10
+ { title: 'VERDICT', detail: 'return findings + summary; the W/F/S resolution decision stays at the human gate' },
11
+ ],
12
+ }
13
+
14
+ // ---------------------------------------------------------------------------
15
+ // FD / STRICT STATE CONTRACT (re-asserted inline — byan-lint-workflows).
16
+ //
17
+ // The in-CLI Workflow tool runs this script OUTSIDE the conversation turn, so
18
+ // BYAN's main-thread hooks (fd-phase-guard, strict-scope-guard, strict-stop-
19
+ // guard, mantra-validate) DO NOT fire here. This script therefore:
20
+ // - NEVER imports/requires _byan/.../lib/fd-state.js and NEVER writes
21
+ // fd-state.json directly (forbidden by the linter).
22
+ // - uses NO wall-clock and NO randomness primitive (wall-clock / RNG /
23
+ // crypto) — they break resume; any timestamp/id arrives via `args`.
24
+ // - returns DATA only. The orchestrating skill is the human-gated conductor;
25
+ // IT records FD/strict state via the byan_fd_* / byan_strict_* MCP tools AT
26
+ // the gate, and IT presents the source step-06 [W]alk/[F]ix/[S]kip menu.
27
+ // The product artifacts (the edited source files, and the tech-spec Status /
28
+ // Review Notes when mode A) are written by the execute/self-check leaves — that
29
+ // is the workflow's output, not BYAN platform state.
30
+ // ---------------------------------------------------------------------------
31
+
32
+ // Convergence guard — mirror of step-03 prose rule "3 consecutive failures on
33
+ // the same task -> HALT". The sandbox forbids import, so the tiny counter is
34
+ // inlined; it turns the prose into a real cap the model cannot silently overrun.
35
+ const MAX_TASK_CYCLES = 3
36
+ function taskConvergenceGuard({ cycles, passed, maxCycles = MAX_TASK_CYCLES }) {
37
+ if (passed) return { done: true, halted: false, reason: 'task-green' }
38
+ if (cycles >= maxCycles) return { done: true, halted: true, reason: `halt: ${maxCycles} consecutive failures on the same task` }
39
+ return { done: false, halted: false, reason: 'retry' }
40
+ }
41
+
42
+ // Hard cap on the number of tasks we will drive in one run (the source loops
43
+ // "for each task" with no count; this bounds the outer loop for a resumable run).
44
+ const MAX_TASKS = (args && Number.isInteger(args.maxTasks) && args.maxTasks > 0) ? args.maxTasks : 25
45
+
46
+ const MODE_SCHEMA = {
47
+ type: 'object',
48
+ required: ['executionMode', 'baselineCommit'],
49
+ properties: {
50
+ executionMode: { type: 'string', enum: ['tech-spec', 'direct'], description: '"tech-spec" if user gave a spec path, else "direct"' },
51
+ techSpecPath: { type: 'string', description: 'path to the tech-spec file when executionMode is tech-spec; empty otherwise' },
52
+ baselineCommit: { type: 'string', description: 'git rev-parse HEAD at start, or "NO_GIT"' },
53
+ projectContextLoaded: { type: 'boolean', description: 'true if a **/project-context.md was found and loaded' },
54
+ escalationSignals: { type: 'array', items: { type: 'string' }, description: 'step-01 escalation signals detected in the direct request (multi-component, system-level, uncertainty, multi-layer, extended timeframe)' },
55
+ escalationLevel: { type: 'integer', description: '0 = none, 0-2 = focused multi-component, 3+ = platform/system work' },
56
+ escalationAdvice: { type: 'string', enum: ['proceed', 'suggest-plan', 'suggest-bmad'], description: 'what the human gate should consider: proceed = execute directly; suggest-plan = quick-spec; suggest-bmad = full PRD flow' },
57
+ },
58
+ }
59
+
60
+ const PLAN_SCHEMA = {
61
+ type: 'object',
62
+ required: ['tasks', 'acceptanceCriteria'],
63
+ properties: {
64
+ filesToModify: { type: 'array', items: { type: 'string' } },
65
+ patterns: { type: 'array', items: { type: 'string' }, description: 'code style / conventions / error-handling / test patterns observed' },
66
+ dependencies: { type: 'array', items: { type: 'string' } },
67
+ tasks: { type: 'array', items: { type: 'string' }, description: 'ordered task list synthesized from the request' },
68
+ acceptanceCriteria: { type: 'array', items: { type: 'string' }, description: 'AC inferred from the user request' },
69
+ },
70
+ }
71
+
72
+ const TASKS_SCHEMA = {
73
+ type: 'object',
74
+ required: ['tasks'],
75
+ properties: {
76
+ tasks: { type: 'array', items: { type: 'string' }, description: 'ordered task list to implement' },
77
+ acceptanceCriteria: { type: 'array', items: { type: 'string' } },
78
+ },
79
+ }
80
+
81
+ const VERIFY_SCHEMA = {
82
+ type: 'object',
83
+ required: ['passed'],
84
+ properties: {
85
+ passed: { type: 'boolean', description: 'true ONLY if this task\'s tests pass with no regressions AND its acceptance criteria are met' },
86
+ blocking: { type: 'array', items: { type: 'string' }, description: 'blocking issues when not passed' },
87
+ summary: { type: 'string' },
88
+ },
89
+ }
90
+
91
+ const SELF_CHECK_SCHEMA = {
92
+ type: 'object',
93
+ required: ['tasksComplete', 'testsPassing', 'acSatisfied', 'patternsFollowed'],
94
+ properties: {
95
+ tasksComplete: { type: 'boolean' },
96
+ testsPassing: { type: 'boolean' },
97
+ acSatisfied: { type: 'boolean' },
98
+ patternsFollowed: { type: 'boolean' },
99
+ filesModified: { type: 'array', items: { type: 'string' } },
100
+ issues: { type: 'array', items: { type: 'string' }, description: 'any self-audit gaps; empty if clean' },
101
+ summary: { type: 'string' },
102
+ },
103
+ }
104
+
105
+ const FINDING = {
106
+ type: 'object',
107
+ required: ['id', 'severity', 'validity', 'description'],
108
+ properties: {
109
+ id: { type: 'string', description: 'F1, F2, ...' },
110
+ severity: { type: 'string', enum: ['Critical', 'High', 'Medium', 'Low'] },
111
+ validity: { type: 'string', enum: ['real', 'noise', 'undecided'] },
112
+ description: { type: 'string' },
113
+ },
114
+ }
115
+
116
+ const REVIEW_SCHEMA = {
117
+ type: 'object',
118
+ required: ['findings'],
119
+ properties: {
120
+ findings: { type: 'array', items: FINDING, description: 'ALL findings, ordered by severity (Critical first), numbered F1..Fn; do not drop by severity/validity' },
121
+ suspiciousZeroFindings: { type: 'boolean', description: 'true if the review returned zero findings (step-05 says this is suspicious and should HALT for human guidance)' },
122
+ diffSummary: { type: 'string' },
123
+ },
124
+ }
125
+
126
+ // --- inputs (everything non-deterministic arrives via args; no clock/RNG) ---
127
+ const userInput = (args && args.request) || (args && args.input) || ''
128
+ const techSpecArg = (args && args.techSpec) || ''
129
+
130
+ // ===========================================================================
131
+ phase('MODE')
132
+ // step-01: capture baseline, load project-context, classify mode, evaluate the
133
+ // escalation threshold. The human escalation MENU ([P]/[W]/[E]) is NOT executed
134
+ // here — the script records the signal/advice and lets the gate decide.
135
+ const mode = await agent(
136
+ `You are quick-dev step-01 (mode detection). User input: ${JSON.stringify(userInput)}. ` +
137
+ `Tech-spec arg (if any): ${JSON.stringify(techSpecArg)}.\n` +
138
+ `1) Capture baseline: if a git repo, run \`git rev-parse HEAD\` -> baselineCommit; else baselineCommit="NO_GIT". ` +
139
+ `2) Check for a **/project-context.md and report projectContextLoaded. ` +
140
+ `3) Classify executionMode: "tech-spec" if a spec path was provided (set techSpecPath), else "direct". ` +
141
+ `4) For direct mode ONLY, evaluate the escalation signals (multiple components, system-level language, ` +
142
+ `uncertainty about approach, multi-layer scope, extended timeframe; reduced by simplicity markers / single-file focus). ` +
143
+ `Use holistic judgment. Set escalationLevel (0 none, 0-2 focused, 3+ platform) and escalationAdvice ` +
144
+ `(proceed / suggest-plan / suggest-bmad). Do NOT prompt the user — just report.`,
145
+ { label: 'mode-detection', phase: 'MODE', schema: MODE_SCHEMA }
146
+ )
147
+ log(`mode=${mode.executionMode} baseline=${mode.baselineCommit} escalation=${mode.escalationLevel || 0} advice=${mode.escalationAdvice || 'proceed'}`)
148
+
149
+ // ===========================================================================
150
+ phase('CONTEXT')
151
+ // step-02: direct mode only — gather files/patterns/deps and synthesize a plan.
152
+ // Mode A (tech-spec) skips this and extracts tasks/AC from the spec instead.
153
+ let plan
154
+ if (mode.executionMode === 'direct') {
155
+ plan = await agent(
156
+ `quick-dev step-02 (context gathering, DIRECT mode). Request: ${JSON.stringify(userInput)}.\n` +
157
+ `Baseline: ${mode.baselineCommit}. project-context loaded: ${Boolean(mode.projectContextLoaded)}.\n` +
158
+ `Identify the files to modify (glob/grep), the relevant patterns (style, conventions, imports, error handling, test patterns), ` +
159
+ `the dependencies (external libs, internal modules, config files), then synthesize an ordered task list and the inferred ` +
160
+ `acceptance criteria. Return the plan; do NOT ask the user y/n/adjust here (that confirmation is the human gate's job).`,
161
+ { label: 'context-gathering', phase: 'CONTEXT', schema: PLAN_SCHEMA }
162
+ )
163
+ } else {
164
+ plan = await agent(
165
+ `quick-dev (TECH-SPEC mode). Load the tech-spec at ${JSON.stringify(mode.techSpecPath || techSpecArg)} and extract its ` +
166
+ `tasks/subtasks and acceptance criteria verbatim. Return the ordered task list and the AC.`,
167
+ { label: 'extract-tech-spec', phase: 'CONTEXT', schema: TASKS_SCHEMA }
168
+ )
169
+ }
170
+ const tasks = Array.isArray(plan.tasks) ? plan.tasks : []
171
+ log(`tasks=${tasks.length} mode=${mode.executionMode}`)
172
+
173
+ // ===========================================================================
174
+ phase('EXECUTE')
175
+ // step-03: implement EVERY task without stopping for milestones; per task, run a
176
+ // load->implement->test loop with a 3-failure convergence cap (the prose HALT).
177
+ const taskResults = []
178
+ const runnable = tasks.slice(0, MAX_TASKS)
179
+ for (let i = 0; i < runnable.length; i++) {
180
+ const task = runnable[i]
181
+ let cycles = 0
182
+ let verify = { passed: false, blocking: ['not started'] }
183
+ let guard = { done: false, halted: false, reason: 'init' }
184
+ while (true) {
185
+ cycles += 1
186
+ const impl = await agent(
187
+ `quick-dev step-03 execute. Task ${i + 1}/${runnable.length} (attempt ${cycles}): ${JSON.stringify(task)}.\n` +
188
+ `Mode: ${mode.executionMode}. Plan context: ${JSON.stringify(plan)}.\n` +
189
+ `Load the files relevant to THIS task, implement following existing patterns and the project-context rules, ` +
190
+ `handle errors as the codebase does, write tests where appropriate, and run existing tests to catch regressions. ` +
191
+ `Do NOT mark the task [x] unless its tests actually pass. Report what you changed.`,
192
+ { label: `execute-task-${i + 1}-cycle-${cycles}`, phase: 'EXECUTE' }
193
+ )
194
+ verify = await agent(
195
+ `Verify quick-dev task ${i + 1}: ${JSON.stringify(task)}. Implementation notes: ${impl}\n` +
196
+ `Run the relevant tests (infer the command from the repo). passed=true ONLY if this task's tests pass with zero ` +
197
+ `regressions AND its acceptance criteria are met. Otherwise passed=false with the precise blocking issues.`,
198
+ { label: `verify-task-${i + 1}-cycle-${cycles}`, phase: 'EXECUTE', schema: VERIFY_SCHEMA }
199
+ )
200
+ guard = taskConvergenceGuard({ cycles, passed: Boolean(verify && verify.passed) })
201
+ log(`task ${i + 1} cycle ${cycles}: passed=${Boolean(verify && verify.passed)} -> ${guard.reason}`)
202
+ if (guard.done) break
203
+ }
204
+ taskResults.push({ index: i + 1, task, passed: Boolean(verify.passed), halted: guard.halted, cycles, blocking: (verify && verify.blocking) || [] })
205
+ // step-03 HALT condition: stop driving further tasks once a task fails to converge.
206
+ if (guard.halted) {
207
+ log(`HALT: task ${i + 1} did not converge after ${MAX_TASK_CYCLES} attempts`)
208
+ break
209
+ }
210
+ }
211
+ const allTasksPassed = taskResults.length > 0 && taskResults.every((t) => t.passed)
212
+ const halted = taskResults.some((t) => t.halted)
213
+
214
+ // ===========================================================================
215
+ phase('SELF_CHECK')
216
+ // step-04: audit tasks/tests/AC/patterns; update tech-spec status when mode A.
217
+ const selfCheck = await agent(
218
+ `quick-dev step-04 self-check. Mode: ${mode.executionMode}. Tech-spec: ${JSON.stringify(mode.techSpecPath || techSpecArg)}.\n` +
219
+ `Task results: ${JSON.stringify(taskResults)}. AC: ${JSON.stringify(plan.acceptanceCriteria || [])}.\n` +
220
+ `Audit: (1) all tasks marked complete with no silent skips; (2) all tests passing, new tests added where needed; ` +
221
+ `(3) every acceptance criterion demonstrably met incl. edge cases; (4) existing patterns and project-context rules followed. ` +
222
+ `If mode is "tech-spec": load the spec, mark its tasks [x] and set status "Implementation Complete". Report the audit verdict.`,
223
+ { label: 'self-check', phase: 'SELF_CHECK', schema: SELF_CHECK_SCHEMA }
224
+ )
225
+ log(`self-check: tasks=${selfCheck.tasksComplete} tests=${selfCheck.testsPassing} ac=${selfCheck.acSatisfied} patterns=${selfCheck.patternsFollowed}`)
226
+
227
+ // ===========================================================================
228
+ phase('ADVERSARIAL_REVIEW')
229
+ // step-05: build the diff from baselineCommit (incl. new untracked files YOU
230
+ // created), run the adversarial review task, and order + number ALL findings.
231
+ const review = await agent(
232
+ `quick-dev step-05 adversarial review. Baseline: ${mode.baselineCommit}.\n` +
233
+ `1) Construct the full diff of changes since the baseline: if a git hash, \`git diff ${mode.baselineCommit}\` PLUS the full ` +
234
+ `content of any new untracked files YOU created this run (do NOT git add anything; read-only). If "NO_GIT", best-effort diff ` +
235
+ `of the files you touched plus new files. ` +
236
+ `2) Run the adversarial review at _byan/command/review-adversarial-general.xml against that diff (inline the task if no ` +
237
+ `subagent invocation is available). ` +
238
+ `3) Return ALL findings ordered by severity (Critical first), numbered F1..Fn, each with severity + validity (real/noise/undecided) ` +
239
+ `+ description. Do NOT drop findings by severity or validity. If the review returns ZERO findings, set suspiciousZeroFindings=true ` +
240
+ `(step-05 treats zero findings as suspicious and a reason to halt for human guidance).`,
241
+ { label: 'adversarial-review', phase: 'ADVERSARIAL_REVIEW', schema: REVIEW_SCHEMA }
242
+ )
243
+ const findings = Array.isArray(review.findings) ? review.findings : []
244
+ log(`review: findings=${findings.length} suspiciousZero=${Boolean(review.suspiciousZeroFindings)}`)
245
+
246
+ // ===========================================================================
247
+ phase('VERDICT')
248
+ // step-06 is a HUMAN decision ([W]alk-through / [F]ix-auto / [S]kip) plus the
249
+ // final tech-spec "Completed" + Review Notes update. We DO NOT pick a resolution
250
+ // here; we hand the gate the ordered, numbered findings and the run summary.
251
+ const realFindings = findings.filter((f) => f && f.validity === 'real')
252
+ const criticalOrHigh = findings.filter((f) => f && (f.severity === 'Critical' || f.severity === 'High'))
253
+ const status = halted
254
+ ? 'halted-no-convergence'
255
+ : allTasksPassed && selfCheck.tasksComplete && selfCheck.testsPassing && selfCheck.acSatisfied
256
+ ? 'review-ready'
257
+ : 'in-progress'
258
+
259
+ return {
260
+ workflow: 'quick-dev',
261
+ summary: selfCheck.summary || `quick-dev (${mode.executionMode}): ${taskResults.length} task(s) processed, ${findings.length} review finding(s).`,
262
+ mode: mode.executionMode,
263
+ baselineCommit: mode.baselineCommit,
264
+ escalation: { level: mode.escalationLevel || 0, advice: mode.escalationAdvice || 'proceed', signals: mode.escalationSignals || [] },
265
+ status,
266
+ steps: 6,
267
+ tasks: { total: tasks.length, run: taskResults.length, passed: taskResults.filter((t) => t.passed).length, results: taskResults },
268
+ selfCheck,
269
+ review: { total: findings.length, real: realFindings.length, criticalOrHigh: criticalOrHigh.length, suspiciousZeroFindings: Boolean(review.suspiciousZeroFindings), findings },
270
+ // The orchestrating skill presents the step-06 [W]/[F]/[S] resolution menu and,
271
+ // for mode A, writes the tech-spec "Completed" status + Review Notes at the gate.
272
+ needsHumanGate: true,
273
+ }
@@ -0,0 +1,261 @@
1
+ export const meta = {
2
+ name: 'sprint-planning',
3
+ description: 'Native port of the BYAN sprint-planning workflow: discover and parse all epic files, build the ordered sprint-status structure (epic / stories / retrospective), apply intelligent never-downgrade status detection, write sprint-status.yaml, then validate coverage and report. Returns a structured verdict; the human gate stays in the orchestrating skill.',
4
+ phases: [
5
+ { title: 'PARSE', detail: 'discover epic*.md files and extract all epics + stories into kebab-case keys' },
6
+ { title: 'STRUCTURE', detail: 'build the ordered development_status map: epic, its stories, its retrospective' },
7
+ { title: 'DETECT', detail: 'intelligent per-story status detection, never downgrade an existing status' },
8
+ { title: 'GENERATE', detail: 'write sprint-status.yaml with metadata as comments AND as parseable fields' },
9
+ { title: 'VALIDATE', detail: 'coverage + legal-status + YAML checks, then count totals and report' },
10
+ ],
11
+ }
12
+
13
+ // ---------------------------------------------------------------------------
14
+ // FD / STRICT STATE CONTRACT (re-asserted inline — byan-lint-workflows).
15
+ //
16
+ // The in-CLI Workflow tool runs this script OUTSIDE the conversation turn, so
17
+ // BYAN's main-thread hooks (fd-phase-guard, strict-scope-guard, mantra-
18
+ // validate) DO NOT fire here. This script therefore:
19
+ // - NEVER imports/requires _byan/.../lib/fd-state.js and NEVER writes
20
+ // fd-state.json directly. No require/import, no fs, no Date/RNG
21
+ // (the sandbox forbids them; clock/RNG break resume — any date is passed
22
+ // in via args.date).
23
+ // - returns DATA only. The orchestrating skill is the human-gated conductor;
24
+ // IT records FD/strict state via the byan_fd_* / byan_strict_* MCP tools
25
+ // AT the gate.
26
+ // The product of THIS workflow is the sprint-status.yaml FILE, written by the
27
+ // GENERATE leaf — that is the artifact, not BYAN platform state.
28
+ // ---------------------------------------------------------------------------
29
+
30
+ // Inputs are passed in (no clock/RNG inside the sandbox). Mirrors the
31
+ // workflow.yaml variables: epics_location, status_file, project_name, date.
32
+ const epicsLocation = (args && args.epicsLocation) || 'planning artifacts folder (look for *epic*.md or epics/index.md)'
33
+ const statusFile = (args && args.statusFile) || '{implementation_artifacts}/sprint-status.yaml'
34
+ const projectName = (args && args.projectName) || '{project_name from _byan/bmm/config.yaml}'
35
+ const projectKey = (args && args.projectKey) || 'NOKEY'
36
+ const trackingSystem = (args && args.trackingSystem) || 'file-system'
37
+ const storyLocation = (args && args.storyLocation) || '{implementation_artifacts}'
38
+ const date = (args && args.date) || '{date passed by orchestrator}'
39
+
40
+ // STEP 1 (+ sub-step 0.5 discovery) — Parse epic files and extract all work items.
41
+ // Mirrors instructions.md step n="1" and the FULL_LOAD discovery: whole doc first
42
+ // (epics.md / bmm-epics.md / *epic*.md), else sharded epics/index.md + epic-N.md.
43
+ phase('PARSE')
44
+ const PARSE_SCHEMA = {
45
+ type: 'object',
46
+ required: ['epics'],
47
+ properties: {
48
+ sourceMode: { type: 'string', enum: ['whole', 'sharded', 'none'], description: 'whole doc, sharded index, or no epics found' },
49
+ epics: {
50
+ type: 'array',
51
+ description: 'every epic discovered, in document order',
52
+ items: {
53
+ type: 'object',
54
+ required: ['num', 'title', 'stories'],
55
+ properties: {
56
+ num: { type: 'integer', description: 'epic number from "## Epic N:"' },
57
+ title: { type: 'string' },
58
+ stories: {
59
+ type: 'array',
60
+ items: {
61
+ type: 'object',
62
+ required: ['key', 'title'],
63
+ properties: {
64
+ key: { type: 'string', description: 'kebab key: "1-1-user-authentication" (period->dash, title kebab-case)' },
65
+ title: { type: 'string', description: 'original story title' },
66
+ },
67
+ },
68
+ },
69
+ },
70
+ },
71
+ },
72
+ },
73
+ }
74
+ const parsed = await agent(
75
+ `You are the sprint-planning workflow (BYAN, Phase 4 implementation). Source of truth: ` +
76
+ `_byan/workflow/simple/4-implementation/sprint-planning/instructions.md (steps 0.5 + 1).\n` +
77
+ `DISCOVER epics under: ${JSON.stringify(epicsLocation)}. FULL_LOAD strategy:\n` +
78
+ `1. Whole document first — epics.md, bmm-epics.md, user-stories.md, or any *epic*.md (fuzzy match).\n` +
79
+ `2. Else sharded — read epics/index.md, then read EVERY epic section file it lists (epic-1.md, epic-2.md ...).\n` +
80
+ `3. If both whole and sharded exist, prefer the whole document.\n` +
81
+ `For each epic file, EXTRACT:\n` +
82
+ `- Epic numbers from headers like "## Epic 1:" / "## Epic 2:".\n` +
83
+ `- Story IDs + titles from "### Story 1.1: User Authentication".\n` +
84
+ `- Convert each story to a kebab key: replace the period with a dash (1.1 -> 1-1) and kebab-case the title, ` +
85
+ `e.g. "### Story 1.1: User Authentication" -> "1-1-user-authentication".\n` +
86
+ `Build the COMPLETE inventory of all epics and stories from ALL epic files. ` +
87
+ `If NO epic file is found, set sourceMode "none" and return an empty epics array (do not invent epics).`,
88
+ { label: 'parse-epics', phase: 'PARSE', schema: PARSE_SCHEMA }
89
+ )
90
+
91
+ // STEP 2 — Build sprint status structure. Mirrors step n="2": per epic emit, in
92
+ // order, epic-{num} (backlog), each story key (backlog), epic-{num}-retrospective
93
+ // (optional). All defaults; status detection happens in DETECT.
94
+ phase('STRUCTURE')
95
+ const STRUCTURE_SCHEMA = {
96
+ type: 'object',
97
+ required: ['entries'],
98
+ properties: {
99
+ entries: {
100
+ type: 'array',
101
+ description: 'ordered development_status entries: epic, its stories, its retrospective, then next epic...',
102
+ items: {
103
+ type: 'object',
104
+ required: ['key', 'kind', 'defaultStatus'],
105
+ properties: {
106
+ key: { type: 'string', description: 'epic-N | story kebab key | epic-N-retrospective' },
107
+ kind: { type: 'string', enum: ['epic', 'story', 'retrospective'] },
108
+ defaultStatus: { type: 'string', enum: ['backlog', 'optional'], description: 'epic/story=backlog, retrospective=optional' },
109
+ },
110
+ },
111
+ },
112
+ },
113
+ }
114
+ const structure = await agent(
115
+ `Step 2 of sprint-planning. From the parsed inventory below, build the ORDERED development_status entry list.\n` +
116
+ `Inventory: ${JSON.stringify(parsed)}\n` +
117
+ `For EACH epic, in epic order, emit in this exact order:\n` +
118
+ `1. epic-{num} -> kind "epic", defaultStatus "backlog"\n` +
119
+ `2. one entry per story, key = the story kebab key -> kind "story", defaultStatus "backlog"\n` +
120
+ `3. epic-{num}-retrospective -> kind "retrospective", defaultStatus "optional"\n` +
121
+ `Then move to the next epic. Do NOT add or drop any item relative to the inventory.`,
122
+ { label: 'build-structure', phase: 'STRUCTURE', schema: STRUCTURE_SCHEMA }
123
+ )
124
+
125
+ // STEP 3 — Apply intelligent status detection. Mirrors step n="3": for each story
126
+ // check {story_location}/{key}.md -> at least ready-for-dev; if a prior status_file
127
+ // exists with a MORE advanced status, preserve it (NEVER downgrade).
128
+ phase('DETECT')
129
+ const DETECT_SCHEMA = {
130
+ type: 'object',
131
+ required: ['entries'],
132
+ properties: {
133
+ entries: {
134
+ type: 'array',
135
+ items: {
136
+ type: 'object',
137
+ required: ['key', 'kind', 'status'],
138
+ properties: {
139
+ key: { type: 'string' },
140
+ kind: { type: 'string', enum: ['epic', 'story', 'retrospective'] },
141
+ status: { type: 'string', enum: ['backlog', 'ready-for-dev', 'in-progress', 'review', 'done', 'optional'] },
142
+ reason: { type: 'string', description: 'why this status (file found / preserved from prior status-file / default)' },
143
+ },
144
+ },
145
+ },
146
+ preservedCount: { type: 'integer', description: 'entries whose status was preserved from a more-advanced prior status-file' },
147
+ },
148
+ }
149
+ const detected = await agent(
150
+ `Step 3 of sprint-planning — intelligent status detection. Ordered entries (all defaults):\n` +
151
+ `${JSON.stringify(structure)}\n` +
152
+ `Story location: ${JSON.stringify(storyLocation)}. Existing status file (if any): ${JSON.stringify(statusFile)}.\n` +
153
+ `Rules, applied per entry:\n` +
154
+ `- STORY: if ${JSON.stringify(storyLocation)}/{key}.md exists, upgrade to at least "ready-for-dev".\n` +
155
+ `- PRESERVATION (never downgrade): if a prior ${JSON.stringify(statusFile)} exists and holds a MORE advanced status ` +
156
+ `for this key, KEEP that status. Story order: backlog < ready-for-dev < in-progress < review < done. ` +
157
+ `Epic order: backlog < in-progress < done. Retrospective: optional <-> done.\n` +
158
+ `- Items with no signal keep their default (backlog / optional).\n` +
159
+ `Return every entry with its resolved status and a short reason, preserving the input order. ` +
160
+ `Count how many statuses were preserved from a prior file in preservedCount.`,
161
+ { label: 'detect-status', phase: 'DETECT', schema: DETECT_SCHEMA }
162
+ )
163
+
164
+ // STEP 4 — Generate the sprint status file. Mirrors step n="4" and the template:
165
+ // metadata appears TWICE (as # comments for docs AND as YAML key:value for
166
+ // parsing), then the ordered development_status block.
167
+ phase('GENERATE')
168
+ const GENERATE_SCHEMA = {
169
+ type: 'object',
170
+ required: ['written', 'path'],
171
+ properties: {
172
+ written: { type: 'boolean', description: 'true once sprint-status.yaml has been written' },
173
+ path: { type: 'string', description: 'absolute path of the written status file' },
174
+ metadataDuplicated: { type: 'boolean', description: 'true if metadata appears both as # comments AND as YAML fields' },
175
+ entryCount: { type: 'integer', description: 'number of development_status lines written' },
176
+ },
177
+ }
178
+ const generated = await agent(
179
+ `Step 4 of sprint-planning — write the status file. Resolved entries (preserve order):\n` +
180
+ `${JSON.stringify(detected)}\n` +
181
+ `Target: ${JSON.stringify(statusFile)}.\n` +
182
+ `Write valid YAML following sprint-status-template.yaml. CRITICAL: metadata appears TWICE — ` +
183
+ `first as a # comment header (generated / project / project_key / tracking_system / story_location, ` +
184
+ `plus the STATUS DEFINITIONS and WORKFLOW NOTES comment block), then again as parseable YAML key:value fields:\n` +
185
+ ` generated: ${date}\n project: ${projectName}\n project_key: ${projectKey}\n` +
186
+ ` tracking_system: ${trackingSystem}\n story_location: ${JSON.stringify(storyLocation)}\n` +
187
+ `Then a development_status: map with every entry "key: status", in order ` +
188
+ `(epic, its stories, its retrospective, blank line, next epic...). ` +
189
+ `Write the complete file to disk and report the path and entry count.`,
190
+ { label: 'generate-status-file', phase: 'GENERATE', schema: GENERATE_SCHEMA }
191
+ )
192
+
193
+ // STEP 5 — Validate and report. Mirrors step n="5" + checklist.md: coverage both
194
+ // ways, retrospective per epic, legal statuses, valid YAML, then totals.
195
+ phase('VALIDATE')
196
+ const VALIDATE_SCHEMA = {
197
+ type: 'object',
198
+ required: ['passed', 'checks', 'totals'],
199
+ properties: {
200
+ passed: { type: 'boolean', description: 'true only if EVERY coverage/legal/YAML check holds' },
201
+ checks: {
202
+ type: 'object',
203
+ required: ['everyEpicPresent', 'everyStoryPresent', 'everyEpicHasRetro', 'noExtraItems', 'allStatusesLegal', 'validYaml'],
204
+ properties: {
205
+ everyEpicPresent: { type: 'boolean' },
206
+ everyStoryPresent: { type: 'boolean' },
207
+ everyEpicHasRetro: { type: 'boolean' },
208
+ noExtraItems: { type: 'boolean', description: 'no status-file item absent from the epic files' },
209
+ allStatusesLegal: { type: 'boolean', description: 'every status value matches the state machine' },
210
+ validYaml: { type: 'boolean' },
211
+ },
212
+ },
213
+ totals: {
214
+ type: 'object',
215
+ required: ['epicCount', 'storyCount'],
216
+ properties: {
217
+ epicCount: { type: 'integer' },
218
+ storyCount: { type: 'integer' },
219
+ epicsInProgress: { type: 'integer' },
220
+ storiesDone: { type: 'integer' },
221
+ },
222
+ },
223
+ failures: { type: 'array', items: { type: 'string' }, description: 'specific check failures, empty if passed' },
224
+ },
225
+ }
226
+ const validation = await agent(
227
+ `Step 5 of sprint-planning — validate and report. Compare the epic-file inventory against the written status file.\n` +
228
+ `Inventory: ${JSON.stringify(parsed)}\nResolved entries: ${JSON.stringify(detected)}\nWrite result: ${JSON.stringify(generated)}\n` +
229
+ `Run every check (per checklist.md):\n` +
230
+ `- everyEpicPresent: each epic in the epic files appears as epic-N in the status file.\n` +
231
+ `- everyStoryPresent: each story in the epic files appears (kebab key) in the status file.\n` +
232
+ `- everyEpicHasRetro: each epic has an epic-N-retrospective entry.\n` +
233
+ `- noExtraItems: no status-file item is absent from the epic files.\n` +
234
+ `- allStatusesLegal: every status matches the state machine (epic: backlog/in-progress/done; ` +
235
+ `story: backlog/ready-for-dev/in-progress/review/done; retro: optional/done).\n` +
236
+ `- validYaml: the file parses as YAML.\n` +
237
+ `Count totals: epicCount, storyCount, epicsInProgress, storiesDone. ` +
238
+ `passed = true only if ALL six checks hold; otherwise list specific failures.`,
239
+ { label: 'validate-and-report', phase: 'VALIDATE', schema: VALIDATE_SCHEMA }
240
+ )
241
+
242
+ // Single top-level return — DATA only. The orchestrating skill presents this at
243
+ // the human gate (review the generated file, then track development) and records
244
+ // FD/strict state via MCP. No platform state is touched here.
245
+ return {
246
+ workflow: 'sprint-planning',
247
+ summary: `Generated sprint-status from ${parsed.epics.length} epic(s); validation ${validation.passed ? 'PASSED' : 'FAILED'}.`,
248
+ steps: 5,
249
+ statusFile: generated.path || statusFile,
250
+ totals: validation.totals,
251
+ validationPassed: validation.passed,
252
+ failures: validation.failures || [],
253
+ needsHumanGate: true,
254
+ nextSteps: [
255
+ 'Review the generated sprint-status.yaml',
256
+ 'Use it to track development progress',
257
+ 'Agents update statuses as they work',
258
+ 'Re-run to refresh auto-detected statuses',
259
+ ],
260
+ result: { parsed, detected, generated, validation },
261
+ }