Browse Source

G-159: goals-system v4 - Follow-on disposition contract + tooling

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.com
master
Julian Noble 7 days ago
parent
commit
fb84c83ac3
  1. 2
      GOALS-archive.md
  2. 8
      GOALS.md
  3. 22
      goals/AGENTS.md
  4. 143
      scriptlib/developer/goals_followons.tcl
  5. 29
      scriptlib/developer/goals_lint.tcl

2
GOALS-archive.md

@ -14,7 +14,7 @@ Acceptance: <as achieved>
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-<new id>`.
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-<new id>`.
(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.)

8
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 <date>` 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 <date>`, 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 <date>` 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 <date>`, 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-<id>`) 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-<id>` or `abandoned` includes a test sweep: search the tree (at minimum `src/tests`) for `G-<old id>` 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 <p...>` 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: <paths/terms>)". 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 <p...>` 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: <paths/terms>)". 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-<new id>` (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: <item> => <disposition>` lines (closed vocabulary: open, goal G-<id>, landed YYYY-MM-DD (<where>), declined YYYY-MM-DD (<why>)); 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-<new id>`; 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

22
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-<id>-<slug>.md``goals/archive/G-<id>-<slug>.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-<slug>.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 <thing> - see goals/archive/G-x-<slug>.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-<slug>.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 <thing> - see goals/archive/G-x-<slug>.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-<id>` (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: <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>)
- Dispositions (closed vocabulary): `open` - not yet acted on; `goal G-<id>` - 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-<id>` 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-<id>` 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 <p...>` 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 <G-id>` 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)

143
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: <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

29
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: <item> => open | goal G-<id> |
# landed YYYY-MM-DD [(<where>)] | declined YYYY-MM-DD [(<why>)]'; a
# 'goal G-<id>' 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: <item> => open | goal G-<id> | landed YYYY-MM-DD (<where>) | declined YYYY-MM-DD (<why>)'"
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

Loading…
Cancel
Save