feat: scaffold BuildPulse v0.1 cockpit
This commit is contained in:
@@ -0,0 +1,148 @@
|
||||
import type { AppState, Feature, FeatureColumn, PulseEvent } from '../../store/types'
|
||||
import { formatDateTime } from '../../utils/format'
|
||||
|
||||
const columnLabels: Record<FeatureColumn, string> = {
|
||||
now: 'Now',
|
||||
next: 'Next',
|
||||
later: 'Later',
|
||||
done: 'Done',
|
||||
}
|
||||
|
||||
const groupFeatures = (features: Feature[]) => ({
|
||||
now: features.filter((feature) => feature.column === 'now'),
|
||||
next: features.filter((feature) => feature.column === 'next'),
|
||||
later: features.filter((feature) => feature.column === 'later'),
|
||||
done: features.filter((feature) => feature.column === 'done'),
|
||||
})
|
||||
|
||||
const getFeatureLabel = (features: Feature[], featureId?: string) => {
|
||||
if (!featureId) return '—'
|
||||
return features.find((feature) => feature.id === featureId)?.title ?? `${featureId} (missing feature)`
|
||||
}
|
||||
|
||||
const renderFeature = (feature: Feature) => {
|
||||
const criteria = feature.acceptance_criteria.length
|
||||
? feature.acceptance_criteria.map((item) => ` - ${item}`).join('\n')
|
||||
: ' - None yet'
|
||||
|
||||
return [
|
||||
`- **${feature.title}**`,
|
||||
` - Description: ${feature.description || '—'}`,
|
||||
` - Priority: ${feature.priority}`,
|
||||
` - Status: ${feature.status}`,
|
||||
' - Acceptance Criteria:',
|
||||
criteria,
|
||||
` - Scope Notes: ${feature.scope_notes || '—'}`,
|
||||
].join('\n')
|
||||
}
|
||||
|
||||
const sortPulsesNewestFirst = (pulses: PulseEvent[]) =>
|
||||
[...pulses].sort((a, b) => b.timestamp.localeCompare(a.timestamp))
|
||||
|
||||
export const createJsonExport = (state: AppState) =>
|
||||
JSON.stringify(
|
||||
{
|
||||
schema_version: state.schema_version,
|
||||
exported_at: new Date().toISOString(),
|
||||
project: state.project,
|
||||
features: state.features,
|
||||
parking_lot: state.parking_lot,
|
||||
pulses: state.pulses,
|
||||
settings: state.settings,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
)
|
||||
|
||||
export const createPulseJsonl = (state: AppState) => state.pulses.map((pulse) => JSON.stringify(pulse)).join('\n')
|
||||
|
||||
export const createMarkdownPackage = (state: AppState) => {
|
||||
const grouped = groupFeatures(state.features)
|
||||
const recentPulses = sortPulsesNewestFirst(state.pulses).slice(0, 8)
|
||||
|
||||
const projectSummary = `# Project Summary\n\n## Name\n${state.project.name}\n\n## One-Line Pitch\n${state.project.one_line_pitch || '—'}\n\n## Description\n${state.project.description || '—'}\n\n## Current Goal\n${state.project.current_goal || '—'}\n\n## Notes\n${state.project.notes || '—'}\n`
|
||||
|
||||
const featurePlan = ['# Feature Plan']
|
||||
;(['now', 'next', 'later', 'done'] as FeatureColumn[]).forEach((column) => {
|
||||
featurePlan.push(`\n## ${columnLabels[column]}`)
|
||||
const features = grouped[column]
|
||||
if (!features.length) {
|
||||
featurePlan.push('\n_No features in this column yet._')
|
||||
return
|
||||
}
|
||||
|
||||
featurePlan.push('', ...features.map(renderFeature))
|
||||
})
|
||||
|
||||
const parkingLot = [
|
||||
'# Parking Lot',
|
||||
'',
|
||||
...(state.parking_lot.length
|
||||
? state.parking_lot.map(
|
||||
(item) =>
|
||||
`- **${item.title}**\n - Description: ${item.description || '—'}\n - Reason parked: ${item.reason_parked || '—'}\n - Possible future placement: ${item.possible_future_placement || '—'}\n - Risk level: ${item.risk_level}`,
|
||||
)
|
||||
: ['_No parked ideas yet._']),
|
||||
].join('\n')
|
||||
|
||||
const pulseLog = [
|
||||
'# Pulse Log',
|
||||
'',
|
||||
...(sortPulsesNewestFirst(state.pulses).length
|
||||
? sortPulsesNewestFirst(state.pulses).map(
|
||||
(pulse) =>
|
||||
`- **${formatDateTime(pulse.timestamp)}** — ${pulse.pulse_type}\n - Feature: ${getFeatureLabel(state.features, pulse.feature_id)}\n - Source/Agent: ${pulse.source} / ${pulse.agent_id}\n - Message: ${pulse.message}\n - Confidence: ${pulse.confidence_score}\n - Evidence: ${pulse.evidence_refs.length ? pulse.evidence_refs.join('; ') : '—'}`,
|
||||
)
|
||||
: ['_No pulse events yet._']),
|
||||
].join('\n')
|
||||
|
||||
const claudeContext = [
|
||||
'# AI Coding Context',
|
||||
'',
|
||||
'## Project',
|
||||
`${state.project.name} — ${state.project.one_line_pitch || '—'}`,
|
||||
'',
|
||||
'## Current Goal',
|
||||
state.project.current_goal || '—',
|
||||
'',
|
||||
'## Active Features',
|
||||
grouped.now.length ? grouped.now.map(renderFeature).join('\n\n') : '_None yet._',
|
||||
'',
|
||||
'## Next Features',
|
||||
grouped.next.length ? grouped.next.map(renderFeature).join('\n\n') : '_None yet._',
|
||||
'',
|
||||
'## Done Features',
|
||||
grouped.done.length ? grouped.done.map(renderFeature).join('\n\n') : '_None yet._',
|
||||
'',
|
||||
'## Parking Lot / Do Not Implement Yet',
|
||||
state.parking_lot.length
|
||||
? state.parking_lot
|
||||
.map((item) => `- ${item.title} — ${item.reason_parked || item.description || 'Parked for later.'}`)
|
||||
.join('\n')
|
||||
: '_No parked ideas yet._',
|
||||
'',
|
||||
'## Recent Pulse Events',
|
||||
recentPulses.length
|
||||
? recentPulses
|
||||
.map(
|
||||
(pulse) =>
|
||||
`- ${formatDateTime(pulse.timestamp)} — ${pulse.pulse_type} — ${pulse.message} (${getFeatureLabel(state.features, pulse.feature_id)})`,
|
||||
)
|
||||
.join('\n')
|
||||
: '_No recent pulse events yet._',
|
||||
'',
|
||||
'## Instructions for AI Developer',
|
||||
'- Only work on the selected feature.',
|
||||
'- Do not implement Parking Lot items.',
|
||||
'- Preserve working behavior.',
|
||||
'- Report changes and test steps.',
|
||||
].join('\n')
|
||||
|
||||
return {
|
||||
'PROJECT_SUMMARY.md': projectSummary,
|
||||
'FEATURE_PLAN.md': featurePlan.join('\n'),
|
||||
'PARKING_LOT.md': parkingLot,
|
||||
'PULSE_LOG.md': pulseLog,
|
||||
'CLAUDE_CONTEXT.md': claudeContext,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user