Browse Source
goals/AGENTS.md: Follow-ons contract - ## Follow-ons section as the last section of archived detail files, line grammar with closed disposition vocabulary (open | goal G-<id> | landed YYYY-MM-DD (<where>) | declined YYYY-MM-DD (<why>)), disposition-only updates sanctioned as the one edit to archived files (same work unit that acts on the item), archive-flip reference sweep gains step (c) emitting the section. GOALS.md: v4 front matter + version entry, achieved-flip and acting-on-a-follow-on maintenance rules, authoring-workflow disposition-flip duty. GOALS-archive.md: frozen-file carve-out wording. goals_lint.tcl: grammar findings (malformed column-0 Follow-on line, disposition naming a goal in neither index) - fixture-verified both fire. New goals_followons.tcl: advisory disposition report grouping open items first + vocabulary- without-section flags; real-archive baseline 30 flagged files (retrofit is the next change-set). Claude-Session: https://claude.ai/code/session_01QgaxV27VZkmEec7oNbEVFc Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.commaster
5 changed files with 198 additions and 6 deletions
@ -0,0 +1,143 @@
|
||||
#!/usr/bin/env tclsh |
||||
# goals_followons.tcl - advisory follow-on disposition report over the goals archive |
||||
# |
||||
# The goals-system v4 Follow-ons contract (goals/AGENTS.md): archived detail files |
||||
# record deferred items as structured column-0 lines in a '## Follow-ons' section - |
||||
# Follow-on: <item text> => open |
||||
# Follow-on: <item text> => goal G-<id> |
||||
# Follow-on: <item text> => landed YYYY-MM-DD (<where>) |
||||
# Follow-on: <item text> => declined YYYY-MM-DD (<why>) |
||||
# - and the disposition is flipped (the one sanctioned edit to an archived detail |
||||
# file) in the same work unit that acts on the item. |
||||
# |
||||
# This tool answers "which follow-ons have not been acted on": |
||||
# report (default) enumerate every structured Follow-on line in |
||||
# goals/archive/G-*.md grouped by disposition, open items first; then |
||||
# advisory flags: archived files carrying follow-on VOCABULARY |
||||
# (follow-on/follow-up/loose end/parked; word-bounded, |
||||
# case-insensitive) with no '## Follow-ons' section - the drift |
||||
# signature of a file whose deferred items were never structured. |
||||
# Malformed Follow-on lines are listed for visibility, but judging |
||||
# them (as fatal findings) is goals_lint.tcl's job. |
||||
# |
||||
# Heuristic flags are advisory by design (vocabulary in historical prose is |
||||
# legitimate); a flagged file is silenced by giving it a '## Follow-ons' section, |
||||
# with real items or with a '(none - ...)' prose note. |
||||
# |
||||
# Plain tclsh (8.6+ or 9), no package dependencies - runnable from any checkout: |
||||
# tclsh scriptlib/developer/goals_followons.tcl [report] [repo-root] |
||||
# Always exit 0 when the scan ran (advisory); exit 2 on bad invocation/root. |
||||
|
||||
set args $argv |
||||
if {[llength $args] && [lindex $args 0] eq "report"} { |
||||
set args [lrange $args 1 end] |
||||
} elseif {[llength $args] && ![file isdir [lindex $args 0]]} { |
||||
puts stderr "goals_followons: unknown subcommand '[lindex $args 0]' (only: report)" |
||||
exit 2 |
||||
} |
||||
set root [expr {[llength $args] ? [lindex $args 0] : [file dirname [file dirname [file dirname [file normalize [info script]]]]]}] |
||||
if {![file isfile [file join $root GOALS.md]] || ![file isdir [file join $root goals archive]]} { |
||||
puts stderr "goals_followons: '$root' does not look like the punkshell repo root (need GOALS.md and goals/archive/)" |
||||
exit 2 |
||||
} |
||||
|
||||
proc read_lines {path} { |
||||
set f [open $path r] |
||||
fconfigure $f -encoding utf-8 |
||||
set lines [split [read $f] \n] |
||||
close $f |
||||
return $lines |
||||
} |
||||
|
||||
set followon_re {^Follow-on: (.+) => (open|goal G-(\d+)|landed (\d{4}-\d{2}-\d{2})( \(.*\))?|declined (\d{4}-\d{2}-\d{2})( \(.*\))?)$} |
||||
set vocab_re {(?i)\y(follow[- ]?ons?|follow[- ]?ups?|loose[- ]ends?|parked)\y} |
||||
|
||||
set open_items {} ;# {fid item} |
||||
set goal_items {} ;# {fid gid item} |
||||
set landed_items {} ;# {fid date item} |
||||
set declined_items {} ;# {fid date item} |
||||
set malformed {} ;# {relpath lineno} |
||||
set unstructured {} ;# {relpath hits} |
||||
set nfiles_with 0 |
||||
|
||||
foreach path [lsort [glob -nocomplain -directory [file join $root goals archive] G-*.md]] { |
||||
set fname [file tail $path] |
||||
set relpath goals/archive/$fname |
||||
set fid "" |
||||
regexp {^G-(\d+)-} $fname -> fid |
||||
set lines [read_lines $path] |
||||
set has_section 0 |
||||
set file_has_lines 0 |
||||
set i 0 |
||||
foreach line $lines { |
||||
incr i |
||||
if {$line eq "## Follow-ons"} { set has_section 1 } |
||||
if {![string match "Follow-on:*" $line]} continue |
||||
if {![regexp $followon_re $line -> item dispo gid ldate lwhere ddate dwhy]} { |
||||
lappend malformed [list $relpath $i] |
||||
continue |
||||
} |
||||
set file_has_lines 1 |
||||
if {$dispo eq "open"} { |
||||
lappend open_items [list $fid $item] |
||||
} elseif {$gid ne ""} { |
||||
lappend goal_items [list $fid $gid $item] |
||||
} elseif {$ldate ne ""} { |
||||
lappend landed_items [list $fid $ldate $item] |
||||
} else { |
||||
lappend declined_items [list $fid $ddate $item] |
||||
} |
||||
} |
||||
if {$file_has_lines} { incr nfiles_with } |
||||
set hits [regexp -all $vocab_re [join $lines \n]] |
||||
if {$hits > 0 && !$has_section} { |
||||
lappend unstructured [list $relpath $hits] |
||||
} |
||||
} |
||||
|
||||
set ntotal [expr {[llength $open_items] + [llength $goal_items] + [llength $landed_items] + [llength $declined_items]}] |
||||
puts "goals_followons report: $ntotal structured follow-on(s) in $nfiles_with archived file(s): [llength $open_items] open, [llength $goal_items] goal, [llength $landed_items] landed, [llength $declined_items] declined; [llength $unstructured] unstructured-vocabulary file(s)" |
||||
|
||||
if {[llength $open_items]} { |
||||
puts "\nOPEN (not yet acted on):" |
||||
foreach row [lsort -integer -index 0 $open_items] { |
||||
lassign $row fid item |
||||
puts " G-$fid $item" |
||||
} |
||||
} |
||||
if {[llength $goal_items]} { |
||||
puts "\nBECAME GOALS:" |
||||
foreach row [lsort -integer -index 0 $goal_items] { |
||||
lassign $row fid gid item |
||||
puts " G-$fid -> G-$gid $item" |
||||
} |
||||
} |
||||
if {[llength $landed_items]} { |
||||
puts "\nLANDED (done without a goal):" |
||||
foreach row [lsort -integer -index 0 $landed_items] { |
||||
lassign $row fid date item |
||||
puts " G-$fid $date $item" |
||||
} |
||||
} |
||||
if {[llength $declined_items]} { |
||||
puts "\nDECLINED:" |
||||
foreach row [lsort -integer -index 0 $declined_items] { |
||||
lassign $row fid date item |
||||
puts " G-$fid $date $item" |
||||
} |
||||
} |
||||
if {[llength $malformed]} { |
||||
puts "\nMALFORMED Follow-on line(s) (goals_lint judges these):" |
||||
foreach row $malformed { |
||||
lassign $row relpath lineno |
||||
puts " $relpath line $lineno" |
||||
} |
||||
} |
||||
if {[llength $unstructured]} { |
||||
puts "\nUNSTRUCTURED (follow-on vocabulary, no '## Follow-ons' section - goals/AGENTS.md Follow-ons):" |
||||
foreach row [lsort -index 0 $unstructured] { |
||||
lassign $row relpath hits |
||||
puts " $relpath ($hits vocabulary hit(s))" |
||||
} |
||||
} |
||||
exit 0 |
||||
Loading…
Reference in new issue