Browse Source

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
master
Julian Noble 6 days ago
parent
commit
c6909c3c02
  1. 7
      GOALS.md
  2. 2
      goals/AGENTS.md
  3. 59
      scriptlib/developer/goals_lint.tcl
  4. 2
      src/modules/AGENTS.md

7
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-<ver>.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

2
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 <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).

59
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-<ver>.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: <stem>-<n.n...>[a|b<n...>]<.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]

2
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 `<name>-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 <name> <version>` 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 `<name>-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 <name> <version>` 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 `<name>-<oldversion>` 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 `<projectdir>/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.

Loading…
Cancel
Save