writeonce/.claude/workflows/prebuild-feature.js
shoney.arickathil 66de570888 chore(workflow): add prebuild-feature — the pre-build research fan-out
A reusable named Workflow (.claude/workflows/) that runs the
brainstorm-to-ready groundwork this repo does before any feature code:

- Understand: read the target story (or find the NEXT-PLAN target) +
  scout relevant .dev/reference projects for the concern
- Analyze: one agent per reference project — how it handles the concern,
  gaps vs our planned approach, recommendations (the fiber/Go step,
  generalized)
- Audit: story-format/frontmatter/plans-no-raw-code + dependency-graph /
  status-board consistency
- Consolidate: settle open forks (KISS defaults), fold reference gaps as
  locked requirements, acceptance-criteria gaps, go/no-go on readiness

Parameterized via args {story?, concern?, references?}; grounds every
agent in on-disk files. Does the parallelizable research half; the
fork-settling stays an interactive brainstorm. Invoke:
Workflow({name:'prebuild-feature', args:{...}}) or /workflows.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 2bfbb0ca5ca17cb2d1ead39f93aa42aeb50b1639)
2026-09-15 01:15:31 +02:00

180 lines
9.2 KiB
JavaScript

export const meta = {
name: 'prebuild-feature',
description: 'Pre-build research fan-out: ground a feature story, compare references, audit story discipline, produce a go/no-go brief',
whenToUse: 'Before writing code for a feature/iteration — run the brainstorm-to-ready groundwork as parallel research and get a consolidated pre-build brief',
phases: [
{ title: 'Understand', detail: 'read the target story + scout relevant .dev/reference projects' },
{ title: 'Analyze', detail: 'one agent per reference project vs the feature concern' },
{ title: 'Audit', detail: 'story-format/frontmatter + dependency-graph/status-board consistency' },
{ title: 'Consolidate', detail: 'settle open forks, fold gaps, go/no-go on readiness' },
],
}
/* ---------------------------------------------------------------------------
* Encodes the ritual this repo follows BEFORE any code lands on a feature:
* understand the story -> ground the forks in the actual runtime ->
* compare against .dev/reference implementations for gaps -> lock the
* decisions with KISS defaults -> acceptance criteria + deps/status.
* It does the *parallelizable research* half and hands back a brief; the
* fork-settling itself stays an interactive brainstorm (human in the loop).
*
* Invoke: Workflow({ name: 'prebuild-feature', args: {
* story: 'docs/stories/runtime-v2/09-in-process-tls.md', // optional
* concern: 'outbound TLS client integration', // optional
* references: ['fiber', 'go'] } }) // optional
* With no args it locates the current NEXT PLAN target itself.
* ------------------------------------------------------------------------- */
const story = (args && args.story) || null
const concern = (args && args.concern) || null
const givenRefs = (args && Array.isArray(args.references)) ? args.references : null
const REF_CAP = 6 // keep the fan-out bounded (medium workflow-size guideline)
const UNDERSTAND_SCHEMA = {
type: 'object',
properties: {
storyPath: { type: 'string' },
concern: { type: 'string' },
readiness: { type: 'string' },
lockedDecisions: { type: 'array', items: { type: 'string' } },
openForks: { type: 'array', items: { type: 'string' } },
acceptanceCriteria: { type: 'string' },
outOfScopePresent: { type: 'boolean' },
summary: { type: 'string' },
},
required: ['storyPath', 'concern', 'readiness', 'openForks', 'summary'],
}
const SCOUT_SCHEMA = {
type: 'object',
properties: {
references: { type: 'array', items: { type: 'string' } },
rationale: { type: 'string' },
},
required: ['references'],
}
const REF_SCHEMA = {
type: 'object',
properties: {
project: { type: 'string' },
howItHandles: { type: 'string' },
gapsInOurApproach: { type: 'array', items: { type: 'string' } },
recommendations: { type: 'array', items: { type: 'string' } },
},
required: ['project', 'howItHandles'],
}
const AUDIT_SCHEMA = {
type: 'object',
properties: {
area: { type: 'string' },
ok: { type: 'boolean' },
issues: { type: 'array', items: { type: 'string' } },
},
required: ['area', 'ok', 'issues'],
}
const BRIEF_SCHEMA = {
type: 'object',
properties: {
ready: { type: 'boolean' },
goNoGo: { type: 'string' },
unsettledForks: { type: 'array', items: { type: 'string' } },
recommendedDefaults: { type: 'array', items: { type: 'string' } },
gapsToFold: { type: 'array', items: { type: 'string' } },
acceptanceGaps: { type: 'array', items: { type: 'string' } },
blockers: { type: 'array', items: { type: 'string' } },
summary: { type: 'string' },
},
required: ['ready', 'goNoGo', 'summary'],
}
const CONVENTIONS =
'Repo discipline: story frontmatter is the ONLY source of status (status + readiness); ' +
'story docs carry NO code blocks (plans-no-raw-code); brainstorm to readiness:ready with ' +
'decisions LOCKED and Given/When/Then acceptance criteria + an out-of-scope list before any ' +
'code lands; docs live under ./docs; the dependency graph is docs/00-dependency-graph.md and ' +
'the status board docs/stories/00-status.md. Read CLAUDE.md and docs/stories/00-status.md to confirm.'
phase('Understand')
// The target story: use args.story, else let the agent find the NEXT PLAN target.
const storyClause = story
? `The target story is ${story}.`
: 'No story path was given — read docs/stories/00-status.md, find the current in-progress / NEXT-PLAN feature, and use its story file.'
const concernClause = concern ? `The feature concern is: ${concern}.` : 'Infer the feature concern from the story.'
const [understanding, scout] = await parallel([
() => agent(
`${storyClause} ${concernClause}\n\n` +
`Read that story and the repo conventions. ${CONVENTIONS}\n\n` +
`Report, as data: the resolved story path, the feature concern in one line, the story's ` +
`readiness, the decisions already LOCKED, the OPEN forks still unsettled, whether ` +
`Given/When/Then acceptance criteria and an out-of-scope list are present, and a short summary. ` +
`Do not propose fixes — just report what is and isn't settled.`,
{ label: 'understand-story', phase: 'Understand', agentType: 'general-purpose', schema: UNDERSTAND_SCHEMA },
),
() => agent(
(givenRefs
? `The caller named these reference projects: ${givenRefs.join(', ')}. Confirm each exists under .dev/reference/ and return the ones that do.`
: `List .dev/reference/ (\`ls .dev/reference\`). ${concernClause} `) +
`Pick the reference projects most relevant to studying this concern (at most ${REF_CAP}), newest/most-relevant first. ` +
`Return their directory names and a one-line rationale. Grounded in what actually exists on disk.`,
{ label: 'scout-references', phase: 'Understand', agentType: 'general-purpose', schema: SCOUT_SCHEMA },
),
])
const theConcern = (understanding && understanding.concern) || concern || 'the feature concern'
const theStory = (understanding && understanding.storyPath) || story || '(the NEXT-PLAN story)'
let refs = (scout && scout.references) || givenRefs || []
refs = refs.slice(0, REF_CAP)
if (refs.length === 0) log('No reference projects identified — skipping the reference-analysis fan-out.')
// One research batch: a reference-analysis agent per project + two audit agents,
// all independent, all needed by the consolidation barrier.
const research = await parallel([
...refs.map((r) => () => agent(
`Analyze how the reference project .dev/reference/${r} handles "${theConcern}". ` +
`Read its actual source (grep/read the relevant files). Report: how it handles the concern; ` +
`where writeonce's planned approach in ${theStory} has GAPS or missing safeguards versus it; ` +
`and concrete recommendations. Be specific and cite files. Return raw data, not prose for a human.`,
{ label: `ref:${r}`, phase: 'Analyze', agentType: 'general-purpose', schema: REF_SCHEMA },
)),
() => agent(
`Audit ${theStory} against the repo's STORY DISCIPLINE. ${CONVENTIONS}\n` +
`Check: frontmatter carries status + readiness; NO code fences in the doc; decisions are LOCKED ` +
`(not vague); Given/When/Then acceptance criteria present; out-of-scope list present. ` +
`Report each violation as an issue; ok=true only if clean.`,
{ label: 'audit:story-format', phase: 'Audit', agentType: 'general-purpose', schema: AUDIT_SCHEMA },
),
() => agent(
`Audit consistency between ${theStory}, the dependency graph (docs/00-dependency-graph.md) and the ` +
`status board (docs/stories/00-status.md) for "${theConcern}". Check: the feature's node/row exists, ` +
`its status matches the story frontmatter, and blockers/dependencies named in the story appear in the ` +
`graph. Report mismatches as issues; ok=true only if consistent.`,
{ label: 'audit:deps-status', phase: 'Audit', agentType: 'general-purpose', schema: AUDIT_SCHEMA },
),
])
const refResults = research.slice(0, refs.length).filter(Boolean)
const audits = research.slice(refs.length).filter(Boolean)
phase('Consolidate')
const brief = await agent(
`You are consolidating a PRE-BUILD brief for "${theConcern}" (story ${theStory}) — the go/no-go before code.\n\n` +
`Understanding of the story:\n${JSON.stringify(understanding, null, 2)}\n\n` +
`Reference analyses (gaps vs our approach):\n${JSON.stringify(refResults, null, 2)}\n\n` +
`Story-discipline + deps/status audits:\n${JSON.stringify(audits, null, 2)}\n\n` +
`Produce the brief: the OPEN forks still to settle (each with a recommended KISS default); ` +
`the gaps from the reference analyses worth FOLDING IN as locked requirements before build; ` +
`any acceptance-criteria gaps; blockers; and a clear go/no-go on whether the story is truly ` +
`ready to build. ready=true only if the forks are settled, the audits are clean, and the ` +
`reference gaps are either folded in or explicitly deferred. Ground every point in the inputs above.`,
{ label: 'consolidate-brief', phase: 'Consolidate', effort: 'high', schema: BRIEF_SCHEMA },
)
log(`Pre-build brief for ${theConcern}: ${brief && brief.goNoGo ? brief.goNoGo : '(no verdict)'}`)
return { story: theStory, concern: theConcern, understanding, references: refResults, audits, brief }