From 311bd127f2af9ceaed6ede8366ed7249050303e8 Mon Sep 17 00:00:00 2001 From: rsimpson2 Date: Sat, 4 Apr 2026 08:56:37 -0400 Subject: [PATCH 1/2] fix: improve handoff accuracy and add roadmap validation (#21, #22) - Fix cmd_update_handoff() to populate Completed Work table, remove (none yet) placeholder, and auto-advance Next Phase Context - Add _detect_wave_for_package() to find wave labels from root ROADMAP - Add _advance_next_phase_context() to auto-fill next phase target/goal - Add validate-roadmap command to check deliverable paths exist on disk - Mark telemetry Phase 1 complete in both per-package and root ROADMAPs Closes #21, partially addresses #22 --- ROADMAP.md | 8 +- packages/telemetry/ROADMAP.md | 10 +- scripts/forge-helper.sh | 205 +++++++++++++++++++++++++++++++++- 3 files changed, 210 insertions(+), 13 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 6f33b59..d9cbe3b 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -6,7 +6,7 @@ For detailed task breakdowns, see the per-package ROADMAPs: - [`packages/pipeline/ROADMAP.md`](packages/pipeline/ROADMAP.md) — Phases 0–3 complete, 4–12 remaining - [`packages/mcp/ROADMAP.md`](packages/mcp/ROADMAP.md) — Phases 0–9 complete, Phase 10 optional -- [`packages/telemetry/ROADMAP.md`](packages/telemetry/ROADMAP.md) — Phase 0 complete, 1–5 remaining +- [`packages/telemetry/ROADMAP.md`](packages/telemetry/ROADMAP.md) — Phases 0–1 complete, 2–5 remaining - [`packages/evaluations/ROADMAP.md`](packages/evaluations/ROADMAP.md) — Phase 0 complete, 1–7 remaining --- @@ -27,7 +27,7 @@ Within each wave, the listed package phases reference the detailed tasks in the | Package | Phase | Work | Status | | ----------- | ------------------------ | ----------------------------------------------------------- | ----------- | -| `telemetry` | Phase 1: Storage | Pluggable storage backends (memory, file) | Not started | +| `telemetry` | Phase 1: Storage | Pluggable storage backends (memory, file) | Complete | | `telemetry` | Phase 2: Telemetry Core | `emit()`, `query()`, `getPipelineSummary()` | Not started | | `telemetry` | Phase 3: Constraints | Constraint evaluator, default constraints, builder utils | Not started | | `telemetry` | Phase 4: Integration API | `createTelemetry()`, `onStageEvent()`, `serializeContext()` | Not started | @@ -194,9 +194,9 @@ Wave 1: Telemetry Implementation | ------------- | --------------------------------------------- | ------------------------------------------------------------------- | | `mcp` | Phases 0–9 (functional) | Phase 10 extensions (optional, unblocked) | | `pipeline` | Phases 0–3 (types, roadmap, handoff, helpers) | Phase 4 (orchestrator) — **blocked on Wave 1** for telemetry wiring | -| `telemetry` | Phase 0 (types only) | Phase 1 (storage) — **ready to start** | +| `telemetry` | Phase 0–1 (types, storage layer) | Phase 2 (telemetry core) — **ready to start** | | `evaluations` | Phase 0 (types only) | Phase 1 (datasets) — **ready to start** | -**Immediate priority**: Telemetry Phases 1–4 (Wave 1). This is the critical path — everything else is blocked on or benefits from working telemetry. +**Immediate priority**: Telemetry Phases 2–4 (remainder of Wave 1). This is the critical path — everything else is blocked on or benefits from working telemetry. **Maximum parallelism opportunity**: After Wave 1 completes, Wave 2 offers the most parallelism — pipeline Phase 4 and evaluations Phases 1–4 can all run simultaneously. diff --git a/packages/telemetry/ROADMAP.md b/packages/telemetry/ROADMAP.md index 2c4d67e..df65461 100644 --- a/packages/telemetry/ROADMAP.md +++ b/packages/telemetry/ROADMAP.md @@ -24,16 +24,16 @@ This roadmap breaks down the [telemetry REQUIREMENTS](../../docs/open-forge-tele --- -## Phase 1: Storage Layer +## Phase 1: Storage Layer (COMPLETE) **Goal**: Implement pluggable storage backends starting with the zero-config default. ### Tasks -- [ ] 1.1 Define storage interface [deps: 0.2] [deliverable: `src/storage/StorageBackend.ts` — StorageBackend with append(), query(), count()] -- [ ] 1.2 Implement in-memory storage [deps: 1.1] [deliverable: `src/storage/MemoryStorageBackend.ts` — for testing and lightweight use] -- [ ] 1.3 Implement file-based storage [deps: 1.1] [deliverable: `src/storage/FileStorageBackend.ts` — JSONL append-only file, default zero-config backend] -- [ ] 1.4 Write storage tests [deps: 1.2, 1.3] [deliverable: `tests/storage.test.ts`] +- [x] 1.1 Define storage interface [deps: 0.2] [deliverable: `src/storage/StorageBackend.ts` — StorageBackend with append(), query(), count()] +- [x] 1.2 Implement in-memory storage [deps: 1.1] [deliverable: `src/storage/MemoryStorageBackend.ts` — for testing and lightweight use] +- [x] 1.3 Implement file-based storage [deps: 1.1] [deliverable: `src/storage/FileStorageBackend.ts` — JSONL append-only file, default zero-config backend] +- [x] 1.4 Write storage tests [deps: 1.2, 1.3] [deliverable: `tests/storage.test.ts`] **Parallel Groups**: diff --git a/scripts/forge-helper.sh b/scripts/forge-helper.sh index 698d5b3..5f7357e 100755 --- a/scripts/forge-helper.sh +++ b/scripts/forge-helper.sh @@ -40,6 +40,9 @@ set -euo pipefail # clear-drift-sentinel [--package ] # check-drift-sentinel [--package ] # +# Subcommands (validation): +# validate-roadmap [--package ] +# # Subcommands (general): # status [--phase N] [--package ] # --------------------------------------------------------------------------- @@ -627,6 +630,107 @@ cmd_phase_update_docs() { # Session handoff # --------------------------------------------------------------------------- +# Detect which wave a package+phase belongs to by scanning root ROADMAP.md +_detect_wave_for_package() { + local pkg="$1" phase="$2" + local root_roadmap="ROADMAP.md" + [[ -f "$root_roadmap" ]] || { echo "--"; return; } + + local current_wave="" saw_wave=false + while IFS= read -r line; do + if echo "$line" | grep -qE '^## Wave [0-9]+:'; then + current_wave=$(echo "$line" | sed -E 's/^## (Wave [0-9]+):.*/\1/') + saw_wave=true + continue + fi + if [[ "$saw_wave" == true ]] && echo "$line" | grep -qE '^## ' && ! echo "$line" | grep -qE '^## Wave'; then + break + fi + [[ -n "$current_wave" ]] || continue + if echo "$line" | grep -qE "^\|.*\`${pkg}\`.*Phase ${phase}"; then + echo "$current_wave" + return + fi + done < "$root_roadmap" + echo "--" +} + +# Rewrite the "## Next Phase Context" section with the next incomplete phase +_advance_next_phase_context() { + local handoff_file="$1" pkg_label="$2" + + local next_info + if [[ "$pkg_label" == "root" ]]; then + next_info=$(_next_phase_for "ROADMAP.md") + else + local pkg_roadmap="packages/${pkg_label}/ROADMAP.md" + [[ -f "$pkg_roadmap" ]] || return 0 + next_info=$(_next_phase_for "$pkg_roadmap") + fi + + [[ -n "$next_info" ]] || return 0 + + local npc_start npc_end + npc_start=$(grep -n '^## Next Phase Context' "$handoff_file" | head -1 | cut -d: -f1) + [[ -n "$npc_start" ]] || return 0 + npc_end=$(tail -n +"$npc_start" "$handoff_file" | grep -n '^---$' | head -1 | cut -d: -f1) + [[ -n "$npc_end" ]] || return 0 + npc_end=$(( npc_start + npc_end - 1 )) + + if [[ "$next_info" == "ROADMAP_COMPLETE" ]]; then + local replacement + replacement="## Next Phase Context + +### Target + +All phases complete for @open-forge/${pkg_label}. + +### Goal + +Package roadmap is complete. Proceed to the next package via \`next-work\`. + +### Critical Context + +- Run \`bash scripts/forge-helper.sh next-work\` to find the next actionable item across all packages." + else + local next_phase next_title next_pending next_total + next_phase=$(echo "$next_info" | cut -d'|' -f1) + next_title=$(echo "$next_info" | cut -d'|' -f2) + next_pending=$(echo "$next_info" | cut -d'|' -f3) + next_total=$(echo "$next_info" | cut -d'|' -f4) + + local replacement + replacement="## Next Phase Context + +### Target + +Wave: @open-forge/${pkg_label} ${next_title} + +### Goal + +${next_pending} of ${next_total} tasks remaining. + +### Critical Context + +- Run \`bash scripts/forge-helper.sh phase-tasks --phase ${next_phase} --package ${pkg_label}\` to see pending tasks +- Follow TDD: TYPES → RED → GREEN → REFACTOR → GATES → COMMIT" + fi + + sed -i '' "${npc_start},${npc_end}d" "$handoff_file" + local insert_line=$(( npc_start - 1 )) + + local tmpfile + tmpfile=$(mktemp) + { + head -n "$insert_line" "$handoff_file" + echo "$replacement" + echo "" + echo "---" + tail -n +"$(( insert_line + 1 ))" "$handoff_file" + } > "$tmpfile" + mv "$tmpfile" "$handoff_file" +} + cmd_init_handoff() { assert_feature_branch local handoff_file="HANDOFF.md" @@ -672,20 +776,50 @@ cmd_update_handoff() { date_stamp="$(date +%Y-%m-%d)" local pkg_label="${PACKAGE:-root}" - # Update Current State table in-place (sed -i '' for macOS compat, # delimiter) + # ----------------------------------------------------------------------- + # 1. Update Current State table + # ----------------------------------------------------------------------- sed -i '' "s#Last Completed Phase.*#Last Completed Phase** | ${pkg_label} phase ${phase} |#" "$handoff_file" sed -i '' "s#Last Session.*#Last Session** | ${date_stamp} |#" "$handoff_file" sed -i '' "s#Active Package.*#Active Package** | ${pkg_label} |#" "$handoff_file" - # Update ROADMAP Status based on whether there are remaining tasks if [[ "$completed" == "$total" && "$total" -gt 0 ]]; then sed -i '' "s#ROADMAP Status.*#ROADMAP Status** | Phase ${phase} complete |#" "$handoff_file" else sed -i '' "s#ROADMAP Status.*#ROADMAP Status** | In progress |#" "$handoff_file" fi - # Append changelog entry - local entry="| ${date_stamp} | -- | ${pkg_label} | ${phase} | ${phase_title} -- ${completed}/${total} tasks |" + # ----------------------------------------------------------------------- + # 2. Populate Completed Work table + # ----------------------------------------------------------------------- + local wave_label + wave_label="$(_detect_wave_for_package "$pkg_label" "$phase")" + + sed -i '' '/^| (none yet)/d' "$handoff_file" + + local cw_row="| ${wave_label} | ${pkg_label} | Phase ${phase} | ${phase_title} |" + awk -v row="$cw_row" ' + /^## Completed Work/ { in_cw=1 } + in_cw && /^\|/ { last_table_line=NR } + in_cw && /^---$/ { in_cw=0 } + { lines[NR]=$0 } + END { + for (i=1; i<=NR; i++) { + print lines[i] + if (i == last_table_line) print row + } + } + ' "$handoff_file" > "${handoff_file}.tmp" && mv "${handoff_file}.tmp" "$handoff_file" + + # ----------------------------------------------------------------------- + # 3. Auto-advance Next Phase Context + # ----------------------------------------------------------------------- + _advance_next_phase_context "$handoff_file" "$pkg_label" + + # ----------------------------------------------------------------------- + # 4. Append changelog entry + # ----------------------------------------------------------------------- + local entry="| ${date_stamp} | ${wave_label} | ${pkg_label} | ${phase} | ${phase_title} -- ${completed}/${total} tasks |" echo "$entry" >> "$handoff_file" log "Updated HANDOFF.md with ${pkg_label} phase ${phase} completion" @@ -802,6 +936,65 @@ cmd_check_drift_sentinel() { fi } +# --------------------------------------------------------------------------- +# Validation +# --------------------------------------------------------------------------- + +cmd_validate_roadmap() { + parse_package_flag "$@" + set -- "${REMAINING_ARGS[@]+"${REMAINING_ARGS[@]}"}" + + local roadmap pkg_dir + roadmap="$(resolve_roadmap "$PACKAGE")" + [[ -f "$roadmap" ]] || fail "Roadmap file not found: $roadmap" + + if [[ -n "$PACKAGE" ]]; then + pkg_dir="packages/${PACKAGE}" + else + pkg_dir="." + fi + + local errors=0 checked=0 skipped=0 + while IFS= read -r line; do + if echo "$line" | grep -qE '^\s*-\s*\[x\]' && echo "$line" | grep -qE '\[deliverable:'; then + if ! echo "$line" | grep -qE '\[deliverable:[[:space:]]*`'; then + skipped=$((skipped + 1)) + continue + fi + + local path + path=$(echo "$line" | sed -E 's/.*\[deliverable:[[:space:]]*`([^`]+)`.*/\1/') + [[ -n "$path" ]] || continue + + if echo "$path" | grep -qE ','; then + skipped=$((skipped + 1)) + continue + fi + + checked=$((checked + 1)) + + local target="${pkg_dir}/${path}" + if [[ ! -f "$target" ]] && [[ ! -d "$target" ]]; then + local task_id + task_id=$(echo "$line" | sed -E 's/^\s*-\s*\[x\]\s*([0-9]+\.[0-9]+).*/\1/') + warn "Missing deliverable: ${target} (task ${task_id})" + errors=$((errors + 1)) + fi + fi + done < "$roadmap" + + if [[ "$checked" -eq 0 ]]; then + log "No completed tasks with deliverables found in ${roadmap} (${skipped} skipped — non-path deliverables)" + return 0 + fi + + if [[ "$errors" -gt 0 ]]; then + fail "${errors} deliverable path(s) missing out of ${checked} checked (${skipped} skipped)" + fi + + log "All ${checked} deliverable path(s) valid in ${roadmap} (${skipped} skipped)" +} + # --------------------------------------------------------------------------- # Dispatch # --------------------------------------------------------------------------- @@ -836,6 +1029,9 @@ show_usage() { echo " clear-drift-sentinel Clear sentinel when phase advances" echo " check-drift-sentinel Check sentinel; exits 0=stuck, 1=clear" echo "" + echo "Validation:" + echo " validate-roadmap Check deliverable paths exist on disk" + echo "" echo "General:" echo " status [--phase N] Show per-phase progress summary" echo "" @@ -870,6 +1066,7 @@ main() { check-branch) cmd_check_branch "$@" ;; ensure-branch) cmd_ensure_branch "$@" ;; next-work) cmd_next_work "$@" ;; + validate-roadmap) cmd_validate_roadmap "$@" ;; status) cmd_status "$@" ;; --help|-h) show_usage ;; *) fail "Unknown subcommand: $subcmd. Run with --help for usage." ;; From abfae6bdc47753ca2c972ab060e860d96c0759e3 Mon Sep 17 00:00:00 2001 From: rsimpson2 Date: Sat, 4 Apr 2026 11:43:12 -0400 Subject: [PATCH 2/2] fix: address PR #25 review feedback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - P2: add word boundary to phase regex in _detect_wave_for_package() to prevent Phase 1 from matching Phase 10 - P1: extract and validate ALL backticked paths in multi-path deliverables instead of skipping comma-separated entries - Dedup: guard against duplicate Completed Work entries in cmd_update_handoff() when called multiple times for same phase - Bonus: fix BSD sed compatibility for task_id extraction (\s → [[:space:]]) --- scripts/forge-helper.sh | 58 ++++++++++++++++++++++------------------- 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/scripts/forge-helper.sh b/scripts/forge-helper.sh index 5f7357e..db37107 100755 --- a/scripts/forge-helper.sh +++ b/scripts/forge-helper.sh @@ -647,7 +647,7 @@ _detect_wave_for_package() { break fi [[ -n "$current_wave" ]] || continue - if echo "$line" | grep -qE "^\|.*\`${pkg}\`.*Phase ${phase}"; then + if echo "$line" | grep -qE "^\|.*\`${pkg}\`.*Phase ${phase}([^0-9]|$)"; then echo "$current_wave" return fi @@ -798,18 +798,23 @@ cmd_update_handoff() { sed -i '' '/^| (none yet)/d' "$handoff_file" local cw_row="| ${wave_label} | ${pkg_label} | Phase ${phase} | ${phase_title} |" - awk -v row="$cw_row" ' - /^## Completed Work/ { in_cw=1 } - in_cw && /^\|/ { last_table_line=NR } - in_cw && /^---$/ { in_cw=0 } - { lines[NR]=$0 } - END { - for (i=1; i<=NR; i++) { - print lines[i] - if (i == last_table_line) print row + + if grep -qF "| ${pkg_label} | Phase ${phase} |" "$handoff_file"; then + log "Completed Work already contains ${pkg_label} Phase ${phase}, skipping duplicate" + else + awk -v row="$cw_row" ' + /^## Completed Work/ { in_cw=1 } + in_cw && /^\|/ { last_table_line=NR } + in_cw && /^---$/ { in_cw=0 } + { lines[NR]=$0 } + END { + for (i=1; i<=NR; i++) { + print lines[i] + if (i == last_table_line) print row + } } - } - ' "$handoff_file" > "${handoff_file}.tmp" && mv "${handoff_file}.tmp" "$handoff_file" + ' "$handoff_file" > "${handoff_file}.tmp" && mv "${handoff_file}.tmp" "$handoff_file" + fi # ----------------------------------------------------------------------- # 3. Auto-advance Next Phase Context @@ -962,24 +967,23 @@ cmd_validate_roadmap() { continue fi - local path - path=$(echo "$line" | sed -E 's/.*\[deliverable:[[:space:]]*`([^`]+)`.*/\1/') - [[ -n "$path" ]] || continue + local task_id + task_id=$(echo "$line" | sed -E 's/^[[:space:]]*-[[:space:]]*\[x\][[:space:]]*([0-9]+\.[0-9]+).*/\1/') - if echo "$path" | grep -qE ','; then - skipped=$((skipped + 1)) - continue - fi + local paths + paths=$(echo "$line" | grep -oE '\[deliverable:[^]]*\]' | grep -oE '`[^`]+`' | tr -d '`') + [[ -n "$paths" ]] || continue - checked=$((checked + 1)) + while IFS= read -r path; do + [[ -n "$path" ]] || continue + checked=$((checked + 1)) - local target="${pkg_dir}/${path}" - if [[ ! -f "$target" ]] && [[ ! -d "$target" ]]; then - local task_id - task_id=$(echo "$line" | sed -E 's/^\s*-\s*\[x\]\s*([0-9]+\.[0-9]+).*/\1/') - warn "Missing deliverable: ${target} (task ${task_id})" - errors=$((errors + 1)) - fi + local target="${pkg_dir}/${path}" + if [[ ! -f "$target" ]] && [[ ! -d "$target" ]]; then + warn "Missing deliverable: ${target} (task ${task_id})" + errors=$((errors + 1)) + fi + done <<< "$paths" fi done < "$roadmap"