From c6909c3c022d6932b4e0ae1791f90801d03e7dd6 Mon Sep 17 00:00:00 2001 From: Julian Noble Date: Sat, 25 Jul 2026 20:39:56 +1000 Subject: [PATCH] goals-system v3: goals_lint stale-versioned-reference warning + Scope version-reference guidance New informational pass in goals_lint (stem-sibling repair heuristic): a live-tier reference to a version-suffixed filename that is MISSING while a same-stem sibling exists on disk warns, naming the sibling(s) - the drift signature of manually-versioned module bumps (libunknown), magic-scheme conversions (config) and stamped bootsupport/vendored renames (fauxlink), exactly the classes fixed in the preceding hygiene commit. References with no stem-sibling stay quiet (proposed goals legitimately name future artifacts); archive tier, globs, URLs, absolute paths and 999999.0a1.0 exempt; warnings never fatal (Scope is contract text - the fix stays a directed edit). Verified: clean on the current tree (incl. correctly-silent existing legacy artifacts) and against a synthetic fixture exercising warn + all five silent classes with per-file dedupe. GOALS.md: front matter bumped v2 -> v3; version-section policy broadened per user direction - bump for ANY change that could affect how an LLM interprets or uses the system (contract, grammar, tooling behaviour, output shape); liberal bumping endorsed. Format section gains Scope version-reference guidance (glob or magic-version filename for stamped files; exact versions only in dated prose). goals/AGENTS.md Verification describes the new check; src/modules/AGENTS.md manual-versioning bullet gains bump step (4): the doc reference sweep. Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com --- GOALS.md | 7 +++- goals/AGENTS.md | 2 +- scriptlib/developer/goals_lint.tcl | 59 +++++++++++++++++++++++++++++- src/modules/AGENTS.md | 2 +- 4 files changed, 65 insertions(+), 5 deletions(-) diff --git a/GOALS.md b/GOALS.md index 5482e0d4..ea6245d5 100644 --- a/GOALS.md +++ b/GOALS.md @@ -1,6 +1,6 @@ # Project Goals -Goals-system: v2 (canonical; the "Goals system versions" section below defines it) +Goals-system: v3 (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`). @@ -30,6 +30,8 @@ Every goal has a detail file; it holds the canonical Goal and Acceptance plus an Scope structure: elements are separated by '; ' (semicolon + space) at top level. Each element names one repo path, module area, or short prose qualifier. Commas may appear INSIDE an element - parenthesised sub-lists (repo-wide (bin/, src/vfs/)), brace globs - but never separate elements; goals_lint flags top-level commas (wrap prose enumerations in parens if needed). +Scope version references: files whose names carry a real stamped version drift at every bump or promotion - manually-versioned modules (libunknown-.tm renames on each bump), bootsupport/vendored copies (renamed at promotion/re-vendor). Reference such files by glob (`src/modules/punk/libunknown-*.tm`) or by the stable magic-version source filename (`config-999999.0a1.0.tm`); pin an exact stamped version only in dated detail-file prose, where being a point-in-time observation is honest. goals_lint warns when a versioned reference goes missing while a same-stem sibling exists on disk - the drift signature. + ### Status tags - `proposed` — not yet started; awaiting user confirmation to go `active` - `active` — in progress @@ -65,10 +67,11 @@ When the user asks to "write a goal for X" or "help me draft a goal for Y", do t ## Goals system versions -The front matter of every repo's GOALS.md declares which goals-system contract version it implements; this section is the canonical definition. Bump the version when the contract or required tooling changes; a repo syncing the system ports the delta between its declared version and current. +The front matter of every repo's GOALS.md declares which goals-system contract version it implements; this section is the canonical definition. Bump the version for ANY change that could affect how an LLM (or other consumer) interprets or uses the system - contract elements, grammar, required tooling, tooling behaviour, or the shape of tool output. Bump liberally: high version numbers are fine and cost nothing, while an unbumped semantic change silently desynchronizes consuming repos. A repo syncing the system ports the delta between its declared version and current. - 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. ## Goals diff --git a/goals/AGENTS.md b/goals/AGENTS.md index 81026c44..6d3f0b7a 100644 --- a/goals/AGENTS.md +++ b/goals/AGENTS.md @@ -78,7 +78,7 @@ 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, and for references to goal IDs present in neither index (dangling). +`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_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). diff --git a/scriptlib/developer/goals_lint.tcl b/scriptlib/developer/goals_lint.tcl index 614d1976..adaef87a 100644 --- a/scriptlib/developer/goals_lint.tcl +++ b/scriptlib/developer/goals_lint.tcl @@ -16,7 +16,14 @@ # commas legal only inside parens/braces (sub-lists, brace globs) # Informational (stderr, never fatal): stale references to archived goals lacking # an achieved/archive marker; references to goal IDs present in neither index -# (dangling). For relationship analysis (scope overlap vs cross-references) +# (dangling); stale versioned path references (goals-system v3) - a live-tier +# reference to a version-suffixed filename that does NOT exist while a same-stem +# sibling exists at a different version is almost certainly version drift +# (manually-versioned module bumps, magic-scheme conversions, stamped +# bootsupport/vendored copies renamed at promotion). Genuinely future artifacts +# have no versioned stem-sibling and stay quiet; exists-but-not-current series +# references (an old vendored version still on disk) are deliberately not +# judged. For relationship analysis (scope overlap vs cross-references) # see the sibling goals_xref.tcl. # # Plain tclsh (8.6+ or 9), no package dependencies - runnable from any checkout: @@ -242,6 +249,56 @@ foreach path [lsort [glob -nocomplain -directory [file join $root goals] G-*.md] } } +# --- informational: stale versioned path references (stem-sibling repair) ------- +# Version-suffixed filename references drift by construction: manually-versioned +# modules rename at every bump (libunknown-.tm), modules convert to the magic +# scheme, and bootsupport/vendored copies rename at every promotion/re-vendor. A +# missing referenced file WITH a same-stem sibling on disk is high-confidence +# drift - the warning names the sibling(s) as the suggested repair. A missing file +# with NO sibling is silent (proposed goals legitimately name future artifacts). +# Scanned: GOALS.md + live goals/G-*.md, full text (Scope lines are the contract +# surface; body prose drifts the same way). Skipped: the archive tier +# (point-in-time records), URLs, absolute/out-of-repo paths, glob references, and +# the magic dev version 999999.0a1.0 (a stable name by design). Warnings only - +# Scope lines are contract text, so the fix stays a directed edit. +proc vdrift_glob_escape {s} { + return [string map {\\ \\\\ * \\* ? \\? [ \\[ ] \\]} $s] +} +set vdrift_files [list [file join $root GOALS.md]] +foreach p [lsort [glob -nocomplain -directory [file join $root goals] G-*.md]] { + lappend vdrift_files $p +} +foreach path $vdrift_files { + if {[file tail $path] eq "GOALS.md"} { + set relpath GOALS.md + } else { + set relpath goals/[file tail $path] + } + set reported [dict create] ;# token -> 1 (one warning per reference per file) + foreach line [read_lines $path] { + foreach rawtok [split $line] { + set tok [string trim $rawtok "\"'`()\[\]\{\}<>,;:!?"] + set tok [string trimright $tok "."] + if {$tok eq "" || [string first / $tok] < 0} continue + if {[string match -nocase "*://*" $tok]} continue + if {[regexp {^[A-Za-z]:/} $tok]} continue + if {[string index $tok 0] in {/ ~}} continue + if {[string first * $tok] >= 0 || [string first ? $tok] >= 0} continue + if {[string match "*999999.0a1.0*" $tok]} continue + if {[dict exists $reported $tok]} continue + # version-suffixed FILE tail: -[a|b]<.ext> + set ftail [file tail $tok] + if {![regexp {^(.+)-([0-9]+(?:\.[0-9]+)+(?:[ab][0-9]+(?:\.[0-9]+)*)?)(\.[A-Za-z0-9_]+)$} $ftail -> stem ver ext]} continue + set full [file join $root $tok] + if {[file exists $full]} continue + set sibs [lsort [glob -nocomplain -directory [file dirname $full] -tails -- "[vdrift_glob_escape $stem]-*[vdrift_glob_escape $ext]"]] + if {![llength $sibs]} continue + dict set reported $tok 1 + lappend warnings "$relpath: references $tok (missing) - version drift? same-stem sibling(s) on disk: [join $sibs {, }]" + } + } +} + if {[llength $warnings]} { puts stderr "goals_lint: [llength $warnings] informational warning(s):" puts stderr [join $warnings \n] diff --git a/src/modules/AGENTS.md b/src/modules/AGENTS.md index 20e39497..f4cae987 100644 --- a/src/modules/AGENTS.md +++ b/src/modules/AGENTS.md @@ -321,7 +321,7 @@ return This keeps the changelog discoverable alongside the version number without requiring a separate CHANGES file. - **Bootstrap-tracked files only** — `punkcheck-buildversion.txt`, `punk/repo-buildversion.txt`, `punk/mix-buildversion.txt` (the top-level `punk::mix` file only; `punk::mix::util`, `punk::mix::cli`, and `punk/mix/commandset/*` version independently and are not covered by this check), `punk/tdl-buildversion.txt`, and `punk/args-buildversion.txt` (added by G-030: make.tcl dogfoods punk::args for dispatch/help; only the parsing contract is tracked — the punk::ansi/textblock rendering stack degrades instead): bump at least **minor** whenever a call site inside that file is updated to use a new API, even if the file's own interface is unchanged. `src/make.tcl` reads only these five files to classify bootsupport staleness (major=abort, minor=prompt, patch=silent-proceed); under-bumping one of them to patch when the call-site change is more significant causes `make.tcl` to mis-classify and either wrongly proceed without prompting or wrongly abort. See `src/bootsupport/AGENTS.md` "Bootsupport Staleness Handling" for the full contract. - **All other modules** (including `punk::mix` submodules): call-site updates follow ordinary judgement from the Patch/Minor/Major rules above — a behavior-preserving call-site change is a patch (or no bump if genuinely a no-op); reserve minor for changes that add capability to the module's *own* API. `src/make.tcl` does not read these versions, so there is no automated consequence, but keep the changelog accurate since it is the only record of the module's real semantic version. -- **Manually versioned modules** (currently `punk::libunknown` only; `punk::mix::base` converted to the magic scheme 2026-07-21): the real `major.minor.patch` version is the filename suffix and the `package provide` block value; there is no `-buildversion.txt` and the build does not stamp a version. The Patch/Minor/Major bump rules above apply identically — an agent changing such a module bumps by (1) renaming the file (`git mv`) to the new version, (2) updating the `# Application ` Meta line, the doctools `manpage_begin` version and the provide-block `set version` (where each exists), and (3) appending a changelog comment line to the version-history block in the module header (it substitutes for the buildversion.txt changelog). Before the first bump of such a module, verify nothing requires it by exact version or hardcoded filename (for punk::libunknown: punk_main.tcl and punk::repl glob `libunknown-*.tm` and pick the highest by vcompare, bootsupport's include_modules.config lists it by name only, and all requires are unversioned - verified 2026-07-11). New modules should use the magic version mechanism instead. +- **Manually versioned modules** (currently `punk::libunknown` only; `punk::mix::base` converted to the magic scheme 2026-07-21): the real `major.minor.patch` version is the filename suffix and the `package provide` block value; there is no `-buildversion.txt` and the build does not stamp a version. The Patch/Minor/Major bump rules above apply identically — an agent changing such a module bumps by (1) renaming the file (`git mv`) to the new version, (2) updating the `# Application ` Meta line, the doctools `manpage_begin` version and the provide-block `set version` (where each exists), (3) appending a changelog comment line to the version-history block in the module header (it substitutes for the buildversion.txt changelog), and (4) sweeping current-state doc references to the old filename: grep `GOALS.md`, `goals/` (live tier) and the AGENTS.md tree for `-` and update Scope/guidance references (goals_lint's stale-versioned-reference warning backstops the goals tiers; historical statements and the archive tier stay as written). Before the first bump of such a module, verify nothing requires it by exact version or hardcoded filename (for punk::libunknown: punk_main.tcl and punk::repl glob `libunknown-*.tm` and pick the highest by vcompare, bootsupport's include_modules.config lists it by name only, and all requires are unversioned - verified 2026-07-11). New modules should use the magic version mechanism instead. - Modules with the magic version number must not appear in output paths such as `/modules`. - When referencing ranges, use bounded specs such as `1.2.3-2.0.0`. - Convert loose versions to bounded form in module metadata; helper utilities exist in boot modules for this purpose.