diff --git a/GOALS-archive.md b/GOALS-archive.md index 4ab4259e..026cba43 100644 --- a/GOALS-archive.md +++ b/GOALS-archive.md @@ -14,7 +14,7 @@ Acceptance: The full record (including the Goal statement) lives in the archived detail file. -Do not edit archived entries except to fix a broken path. If an archived goal is reopened, move it back to `GOALS.md` with a new ID and mark the old entry `superseded by G-`. +Do not edit archived records here except to fix a broken path. Archived detail files under `goals/archive/` are likewise frozen, with one exception: `Follow-on:` disposition updates per the `goals/AGENTS.md` Follow-ons contract (goals-system v4). If an archived goal is reopened, move it back to `GOALS.md` with a new ID and mark the old entry `superseded by G-`. (One sanctioned exception: the 2026-07-24 goals-system v2 delimiter normalisation rewrote archived Scope lines from comma to semicolon delimiters under explicit user approval; record content was otherwise unchanged.) diff --git a/GOALS.md b/GOALS.md index d1154d6c..5bac62d1 100644 --- a/GOALS.md +++ b/GOALS.md @@ -1,6 +1,6 @@ # Project Goals -Goals-system: v3 (canonical; the "Goals system versions" section below defines it) +Goals-system: v4 (canonical; the "Goals system versions" section below defines it) This file is the canonical, harness-agnostic index of technical project goals for punkshell. It is referenced from the root `AGENTS.md` Child DOX Index and is a required read for any non-trivial work, so that agents can discover goals whose scope intersects their work. It is deliberately summary-only so that reading it in full stays cheap: each entry carries ID, status, title and Scope plus a detail-file pointer, and the goal's full contract prose (Goal statement and Acceptance criterion) lives in its detail file under `goals/` (see `goals/AGENTS.md`). @@ -44,11 +44,12 @@ Scope version references: files whose names carry a real stamped version drift a - Suggesting is always allowed and encouraged when grounded in the work at hand: a discovered gap, a recurring manual step, a deferred design decision, or a natural follow-on that fits what is currently being worked on. Flag it as a candidate goal in conversation or the completion report, optionally with a drafted entry ready for approval. A suggestion is not an edit - nothing is written to this file or `goals/` until the user approves per the proposal-first rule. A suggested entry carries the same overlap survey as a drafted goal (per the authoring workflow's overlap-survey step): name the overlapping existing goals or state the survey found none. - The goal contract spans both tiers: the index entry (title, status tag, Scope) and the detail file's `Goal:` and `Acceptance:` lines. - Proposal-first: before writing a new goal or changing any contract element in either tier, show the user the proposed wording - the full entry plus detail-file header for a new goal, the changed clause(s) for an edit - and get explicit approval. If the user already supplied or approved the exact wording this session, apply it and report what was written. -- Exception (sanctioned autonomous edit): an agent whose work satisfies a goal's `Acceptance:` (judged against the detail file's criterion, never the index entry alone) must flip that goal to `achieved ` as part of its DOX closeout pass, archive it (entry to `GOALS-archive.md` per that file's format, detail file to `goals/archive/`), and report the flip in its completion summary. If the detail file carries a `## Progress` section, the flip additionally requires its remaining-work list to be resolved — empty, or each item verified satisfied; a partial increment never flips a goal. The flip records its verification evidence (what was verified, on which kits/runtimes, any remaining manual items) in the detail file's body — the `Status:` line stays bare `achieved `, per the header field grammar in `goals/AGENTS.md`. The flip also includes a reference sweep of the live tier (see `goals/AGENTS.md` Archive rules): pending-tense mentions of the archived goal updated to reflect achievement, and any of its notes actionable for a live goal pointed to from that goal's `## Notes`. As part of the same sweep, run `goals_xref.tcl report`: UNLINKED pairs annotated with the archived goal's id under "shared archived refs" lose their only recorded bridge when it leaves the live tier - add a direct `Related:` pointer where the overlap is real (same non-contract Notes mechanism as the sweep). +- Exception (sanctioned autonomous edit): an agent whose work satisfies a goal's `Acceptance:` (judged against the detail file's criterion, never the index entry alone) must flip that goal to `achieved ` as part of its DOX closeout pass, archive it (entry to `GOALS-archive.md` per that file's format, detail file to `goals/archive/`), and report the flip in its completion summary. If the detail file carries a `## Progress` section, the flip additionally requires its remaining-work list to be resolved — empty, or each item verified satisfied; a partial increment never flips a goal. The flip records its verification evidence (what was verified, on which kits/runtimes, any remaining manual items) in the detail file's body — the `Status:` line stays bare `achieved `, per the header field grammar in `goals/AGENTS.md`. The flip records the goal's deferred items as a `## Follow-ons` section of structured disposition lines (`goals/AGENTS.md` Follow-ons). The flip also includes a reference sweep of the live tier (see `goals/AGENTS.md` Archive rules): pending-tense mentions of the archived goal updated to reflect achievement, and any of its notes actionable for a live goal pointed to from that goal's `## Notes`. As part of the same sweep, run `goals_xref.tcl report`: UNLINKED pairs annotated with the archived goal's id under "shared archived refs" lose their only recorded bridge when it leaves the live tier - add a direct `Related:` pointer where the overlap is real (same non-contract Notes mechanism as the sweep). - Agents must not flip `proposed` → `active`. They flag it in their completion report for the user to confirm. Activation freshness: the flagging agent re-runs the overlap survey (`goals_xref.tcl score G-`) as part of the flag - goals drafted in the interval may overlap; relationships found are recorded as `Related:` Notes lines at activation (non-contract content, reported in the completion summary). - Index entries carry no progress: the status tag is the only state the index records. Incremental progress on an `active` goal (what landed, what remains) is recorded in the detail file's `## Progress` section, never as annotations on the index entry. - Marking a goal `superseded by G-` or `abandoned` includes a test sweep: search the tree (at minimum `src/tests`) for `G-` references and for the tests named in the goal's detail-file Acceptance, and record each affected test's disposition in the superseding goal's detail file (or the abandoned goal's own): pinned expectations that transfer to the new goal, pins that stand down to plain behaviour characterization, and any that lapse. The sweep never deletes, skips, or weakens a test on its own — that still requires explicit user direction per `src/tests/AGENTS.md`. - Detail files may be updated without pre-approval when recording findings, decisions, or verification artifacts from work the user directed on that goal or its subject matter; report such updates in the completion summary. Edits to a detail file's `Goal:` or `Acceptance:` lines are contract changes and follow proposal-first. +- Acting on a follow-on recorded in an archived goal's `## Follow-ons` section - drafting a goal from it, or landing/declining it directly at the user's direction - includes flipping that line's disposition in the same work unit (the one sanctioned non-contract edit to archived detail files; see `goals/AGENTS.md` Follow-ons). - If a goal cannot be summarized safely by its title and Scope alone, that is a signal it is really two goals — split it. ## Authoring a new goal (for agents) @@ -61,7 +62,7 @@ When the user asks to "write a goal for X" or "help me draft a goal for Y", do t - **Goal:** In one or a few sentences, what does done look like? Push for an outcome, not an activity ("X compiles to bytecode ≤ 1.10× cost of Y", not "improve compiler performance"). - **Acceptance:** What is the measurable, verifiable pass/fail criterion an agent can check against? If the user cannot state one, propose 2-3 candidate criteria and ask them to pick or refine. - **Status:** Default to `proposed` unless the user says it is already in progress (`active`). -3. **Overlap survey (required before drafting).** Run `tclsh scriptlib/developer/goals_xref.tcl paths ` with the paths/modules the goal will touch, and review `goals_xref.tcl report`'s current unlinked-pair list; read the detail files of every goal the survey surfaces. The survey result is part of the proposal shown to the user: either a `Related:` line per overlapping goal (dependency, consumer, sibling surface, or deliberate non-overlap) or an explicit "overlap survey: no related goals found (checked: )". A draft that leaves an overlapping goal unnamed is a drafting defect. Cross-references follow the dependency-direction convention: the goal that consumes, extends, or replaces points at its predecessor; back-pointers are welcome but optional. +3. **Overlap survey (required before drafting).** Run `tclsh scriptlib/developer/goals_xref.tcl paths ` with the paths/modules the goal will touch, and review `goals_xref.tcl report`'s current unlinked-pair list; read the detail files of every goal the survey surfaces. The survey result is part of the proposal shown to the user: either a `Related:` line per overlapping goal (dependency, consumer, sibling surface, or deliberate non-overlap) or an explicit "overlap survey: no related goals found (checked: )". A draft that leaves an overlapping goal unnamed is a drafting defect. Cross-references follow the dependency-direction convention: the goal that consumes, extends, or replaces points at its predecessor; back-pointers are welcome but optional. If the draft enacts a follow-on recorded in an archived goal's `## Follow-ons` section, the same change-set flips that line's disposition to `goal G-` (see the maintenance rules). 4. Draft the index entry (this file's format) and the detail file (header block per `goals/AGENTS.md`, plus Context/Approach/etc. sections only when there is real content for them) and show both to the user for review, applying them only after approval (per the maintenance rules). Do not set it `active` unless the user confirms. 5. Do not delete or rewrite existing goals to make room for a new one. Append with the next free ID. @@ -72,6 +73,7 @@ The front matter of every repo's GOALS.md declares which goals-system contract v - v1 (2026-07): two-tier contract (summary index + detail files), detail-file header grammar, status tags, goals_lint.tcl, goals_xref.tcl. - v2 (2026-07-24): Scope elements are '; '-separated at top level (lint-enforced); cross-repo goal references drop the G- prefix; goals_xref.tcl gains repo-local configuration (goals/xref.conf - path roots, stop words, file extensions, reference containers, module dirs, bare_path_roots, prefix_anchors). - v3 (2026-07-25): goals_lint.tcl gains the stale-versioned-reference warning (a live-tier reference to a version-suffixed filename that is missing while a same-stem sibling exists on disk is flagged with the sibling(s) named; archive tier, globs, URLs, absolute/out-of-repo paths and the magic dev version 999999.0a1.0 are exempt; informational, never fatal); Scope-authoring guidance added to the Format section (version-stamped files referenced by glob or magic-version filename, exact versions pinned only in dated prose); this bump-policy paragraph broadened from "contract or required tooling changes" to any interpretation-affecting change. +- v4 (2026-08-03): archived-goal follow-on tracking (drafted as G-159): archived detail files carry a `## Follow-ons` section of structured `Follow-on: => ` lines (closed vocabulary: open, goal G-, landed YYYY-MM-DD (), declined YYYY-MM-DD ()); disposition-only updates to archived detail files are sanctioned non-contract edits made in the same work unit that acts on a follow-on; the achieved flip records deferred items in the grammar (reference-sweep step (c)) and the authoring workflow flips an enacted follow-on's disposition to `goal G-`; goals_lint.tcl validates the grammar (malformed line or unknown goal reference = finding); new advisory tool goals_followons.tcl enumerates dispositions and flags archived files carrying follow-on vocabulary with no structured section. ## Goals diff --git a/goals/AGENTS.md b/goals/AGENTS.md index 6d3f0b7a..3cd9ea20 100644 --- a/goals/AGENTS.md +++ b/goals/AGENTS.md @@ -69,19 +69,37 @@ When user-directed work on an `active` goal lands without satisfying its `Accept - `goals/archive/` holds detail files for achieved goals. Archiving happens as part of the achieved flip (see the root `GOALS.md` maintenance rules): the detail file moves `goals/G--.md` → `goals/archive/G--.md` and the index entry moves to `GOALS-archive.md`. Do not rename the ID prefix. - At the achieved flip, record the verification evidence in the detail file body (`## Progress` or `## Notes`): what was verified, on which kits/runtimes, design outcomes worth keeping, and any remaining manual items. The `Status:` line itself carries only `achieved YYYY-MM-DD` (header field grammar above). - The archive move is content-identical: the file lands in `archive/` byte-for-byte as it left `goals/`, with the flip's content edits (Status line, evidence write-up) made before or after the move so the move diff is a pure rename (root `AGENTS.md` "Doc Restructures"). -- Reference sweep at archive time: grep the live tier (`goals/*.md`, `GOALS.md`) for the archived goal's ID. (a) Rewrite pending-tense phrasing ("once G-x lands", "after G-x is achieved") to reflect achievement, with the archive path - e.g. "(G-x, achieved - see goals/archive/G-x-.md)". (b) For each note in the archived detail file carrying actionable content for a live goal (a follow-on, dependency detail, or design decision that goal will need), add a one-line pointer to that live goal's `## Notes`: "G-x (archived) recorded - see goals/archive/G-x-.md". Mentions that are pure history need nothing. Rationale: an archived file is no longer edited, so its forward-pointing insights must be pushed to their targets when it leaves the active set. These are non-contract Notes updates - no pre-approval needed, report in the completion summary. +- Reference sweep at archive time: grep the live tier (`goals/*.md`, `GOALS.md`) for the archived goal's ID. (a) Rewrite pending-tense phrasing ("once G-x lands", "after G-x is achieved") to reflect achievement, with the archive path - e.g. "(G-x, achieved - see goals/archive/G-x-.md)". (b) For each note in the archived detail file carrying actionable content for a live goal (a follow-on, dependency detail, or design decision that goal will need), add a one-line pointer to that live goal's `## Notes`: "G-x (archived) recorded - see goals/archive/G-x-.md". Mentions that are pure history need nothing. (c) Record the goal's deferred items as a `## Follow-ons` section per the Follow-ons contract below: an actionable note with a live-goal home gets `=> goal G-` (alongside its (b) pointer), one with no home gets `=> open`, and a file whose follow-on-like phrasing is purely historical gets the section with a `(none - ...)` prose note. Rationale: an archived file is no longer edited apart from Follow-on disposition updates (see Follow-ons below), so its forward-pointing insights must be pushed to their targets - or recorded as structured open follow-ons - when it leaves the active set. These are non-contract Notes updates - no pre-approval needed, report in the completion summary. - No orphan detail files: every file here (excluding `archive/`, this `AGENTS.md`, and `xref.conf`) corresponds to a `proposed`, `active`, `abandoned` or `superseded` entry in root `GOALS.md`; every file under `archive/` corresponds to a record in `GOALS-archive.md`. +### Follow-ons (archived detail files) + +Archived goals routinely record deferred items - work noted during the goal but deliberately left outside its acceptance. Unstructured, such notes fossilize: nothing records when one is later acted on, and nothing can enumerate the ones that were not. The Follow-ons contract (goals-system v4) makes them trackable. + +- Section: `## Follow-ons` is the LAST section of an archived detail file, written at the achieved flip (reference-sweep step (c) above) or by the one-time G-159 retrofit. It holds zero or more `Follow-on:` lines plus optional brief prose - e.g. `(none - follow-on-like phrasing above is historical description, not open items)` when the file's vocabulary is incidental. Live-tier detail files do not carry the section; a live goal's deferred items are ordinary Notes until its flip. +- Line grammar (one item per line, at column 0; the final ` => ` separates item text from disposition): + + Follow-on: => open + Follow-on: => goal G- + Follow-on: => landed YYYY-MM-DD () + Follow-on: => declined YYYY-MM-DD () + +- Dispositions (closed vocabulary): `open` - not yet acted on; `goal G-` - a drafted goal enacts it (the id may itself archive later; no achieved/archive marker is required inside a disposition); `landed` - done directly without a goal, the parenthetical naming where (version, commit, file); `declined` - deliberately not pursued, the parenthetical saying why. Parentheticals are optional but recommended. +- Disposition updates are the one sanctioned edit to archived detail files: non-contract, made in the SAME work unit that acts on the follow-on (drafting its goal, landing it directly at user direction, or deciding against it), and reported in the completion summary. Item text stays stable once written - only the disposition tail changes. New items are not added post-archive: fresh insight about archived territory belongs to a live goal's Notes. +- Tooling split: `goals_lint.tcl` validates the grammar (a malformed column-0 `Follow-on:` line, or a `goal G-` disposition naming a goal in neither index, is a finding); `goals_followons.tcl report` enumerates dispositions and advisorily flags archived files carrying follow-on vocabulary with no `## Follow-ons` section. + ## Work Guidance LF line endings (per root AGENTS.md user preferences). ## Verification -`tclsh scriptlib/developer/goals_lint.tcl` — run after editing goal files or either index. Validates the detail-file header grammar (including the bare-`Status:` rule), index/detail orphan rules in both tiers, and the Status/Scope mirror consistency (plain tclsh, no dependencies; exit 0 clean, exit 1 with one line per finding). Informational, never fatal: warnings for stale references to archived goals lacking an achieved/archive marker, for references to goal IDs present in neither index (dangling), and (goals-system v3) for stale versioned path references - a live-tier reference to a version-suffixed filename that is missing while a same-stem sibling exists on disk (the drift signature of manually-versioned module bumps, magic-scheme conversions, and stamped bootsupport/vendored renames; the warning names the sibling(s) as the suggested repair, and references with no stem-sibling stay quiet so proposed goals can name future artifacts freely). +`tclsh scriptlib/developer/goals_lint.tcl` — run after editing goal files or either index. Validates the detail-file header grammar (including the bare-`Status:` rule), index/detail orphan rules in both tiers, and the Status/Scope mirror consistency (plain tclsh, no dependencies; exit 0 clean, exit 1 with one line per finding). Informational, never fatal: warnings for stale references to archived goals lacking an achieved/archive marker, for references to goal IDs present in neither index (dangling), and (goals-system v3) for stale versioned path references - a live-tier reference to a version-suffixed filename that is missing while a same-stem sibling exists on disk (the drift signature of manually-versioned module bumps, magic-scheme conversions, and stamped bootsupport/vendored renames; the warning names the sibling(s) as the suggested repair, and references with no stem-sibling stay quiet so proposed goals can name future artifacts freely). Goals-system v4 adds Follow-on grammar validation as findings: a column-0 `Follow-on:` line in any detail file (either tier) that does not match the Follow-ons grammar, or whose `goal G-` disposition names a goal absent from both indexes. `tclsh scriptlib/developer/goals_xref.tcl` - advisory relationship analysis (plain tclsh, no dependencies; always exit 0). Cross-references the live goals against scope overlap: `report` lists UNLINKED overlap pairs (overlapping goals that never reference each other) plus ONE-DIRECTIONAL pairs (back-pointer candidates), with path/term evidence per pair; `paths ` lists the goals whose Scope intersects the given repo paths (the instrument for the "read intersecting goals before editing" step in root `GOALS.md` and for drafting-time overlap surveys); `score ` shows one goal's overlap pairs classified linked/one-directional/unlinked; `refs` lists dangling references. Scoring is IDF-weighted so ubiquitous anchors count for little; it ranks lexical overlap honestly but cannot see purely conceptual relationships - treat its output as a survey aid, never a gate. Repo-specific scoring vocabularies (path roots, stop words, file extensions, reference-container damps, module dirs, bare-root anchoring) may be adjusted per repo via an optional `goals/xref.conf` (key list in the tool's header comment; a template ships at `goals/xref.conf` with every line disabled, so the compiled-in punkshell defaults apply unchanged). +`tclsh scriptlib/developer/goals_followons.tcl report` - advisory follow-on disposition report (plain tclsh, no dependencies; always exit 0). Enumerates every structured `Follow-on:` line in `goals/archive/` grouped by disposition, open items first - the "which follow-ons have not been acted on" answer - then flags archived files that carry follow-on vocabulary (follow-on/follow-up/loose end/parked, word-bounded, case-insensitive) but no `## Follow-ons` section: the drift signature of a file whose deferred items were never structured. Heuristic flags are advisory, never fatal (vocabulary in historical prose is legitimate; a flagged file is silenced by giving it the section, with real items or a `(none - ...)` note). The grammar itself is goals_lint's job. + ## Child DOX Index - `archive/` — detail files for achieved/archived goals (no child AGENTS.md needed; this file's archive rules cover it) diff --git a/scriptlib/developer/goals_followons.tcl b/scriptlib/developer/goals_followons.tcl new file mode 100644 index 00000000..14806237 --- /dev/null +++ b/scriptlib/developer/goals_followons.tcl @@ -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: => open +# Follow-on: => goal G- +# Follow-on: => landed YYYY-MM-DD () +# Follow-on: => declined YYYY-MM-DD () +# - 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 diff --git a/scriptlib/developer/goals_lint.tcl b/scriptlib/developer/goals_lint.tcl index adaef87a..c8b67001 100644 --- a/scriptlib/developer/goals_lint.tcl +++ b/scriptlib/developer/goals_lint.tcl @@ -14,6 +14,12 @@ # - Status:/Scope: mirror lines in detail files match the owning index entry # - Scope delimiter grammar (v2): elements separated by '; ' at top level; # commas legal only inside parens/braces (sub-lists, brace globs) +# - Follow-on line grammar (v4): a column-0 'Follow-on:' line in any detail +# file (either tier) must be 'Follow-on: => open | goal G- | +# landed YYYY-MM-DD [()] | declined YYYY-MM-DD [()]'; a +# 'goal G-' disposition must name a goal present in one of the indexes +# (disposition enumeration + vocabulary heuristics live in the sibling +# goals_followons.tcl) # Informational (stderr, never fatal): stale references to archived goals lacking # an achieved/archive marker; references to goal IDs present in neither index # (dangling); stale versioned path references (goals-system v3) - a live-tier @@ -207,6 +213,29 @@ foreach {dir index indexname achieved_expected} [list \ } } +# --- Follow-on line grammar (goals-system v4) --------------------------------- +# Archived detail files carry '## Follow-ons' sections of structured lines +# (goals/AGENTS.md Follow-ons contract). Grammar violations are findings; the +# disposition enumeration and vocabulary heuristics live in goals_followons.tcl. +set followon_re {^Follow-on: .+ => (open|goal G-(\d+)|landed \d{4}-\d{2}-\d{2}( \(.*\))?|declined \d{4}-\d{2}-\d{2}( \(.*\))?)$} +foreach dir [list goals [file join goals archive]] { + foreach path [lsort [glob -nocomplain -directory [file join $root $dir] G-*.md]] { + set relpath [string map {\\ /} [file join $dir [file tail $path]]] + set i 0 + foreach line [read_lines $path] { + incr i + if {![string match "Follow-on:*" $line]} continue + if {![regexp $followon_re $line -> dispo gid]} { + flag $relpath "malformed Follow-on line (line $i) - want 'Follow-on: => open | goal G- | landed YYYY-MM-DD () | declined YYYY-MM-DD ()'" + continue + } + if {$gid ne "" && ![dict exists $active_index $gid] && ![dict exists $archive_index $gid]} { + flag $relpath "Follow-on disposition names G-$gid (line $i) - no such goal in GOALS.md or GOALS-archive.md" + } + } + } +} + # --- informational: candidate stale references to archived goals --------------- # An active detail file mentioning an archived goal with no achieved/archive/landed # marker on ANY of the mentioning lines has likely missed the archive-time reference