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 1 week 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. 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.) (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 # 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`). 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. - 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. - 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. - 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). - 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. - 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`. - 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. - 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. - 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) ## 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"). - **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. - **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`). - **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. 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. 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. - 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). - 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. - 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 ## 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. - `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). - 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"). - 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`. - 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 ## Work Guidance
LF line endings (per root AGENTS.md user preferences). LF line endings (per root AGENTS.md user preferences).
## Verification ## 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_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 ## Child DOX Index
- `archive/` — detail files for achieved/archived goals (no child AGENTS.md needed; this file's archive rules cover it) - `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 # - Status:/Scope: mirror lines in detail files match the owning index entry
# - Scope delimiter grammar (v2): elements separated by '; ' at top level; # - Scope delimiter grammar (v2): elements separated by '; ' at top level;
# commas legal only inside parens/braces (sub-lists, brace globs) # 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 # Informational (stderr, never fatal): stale references to archived goals lacking
# an achieved/archive marker; references to goal IDs present in neither index # an achieved/archive marker; references to goal IDs present in neither index
# (dangling); stale versioned path references (goals-system v3) - a live-tier # (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 --------------- # --- informational: candidate stale references to archived goals ---------------
# An active detail file mentioning an archived goal with no achieved/archive/landed # 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 # marker on ANY of the mentioning lines has likely missed the archive-time reference

Loading…
Cancel
Save