KDL specs: add a first-class top-level description field; stop leaning on the code-comments header exemption #179
Labels
No labels
burndown-2026-06
sunday-sprint
coherence-core
consult
headless
interactive
P0
P1
P2
P3
P4
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
coilyco-flight-deck/cli-guard#179
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
The
code-commentscatalog hook (authored in agentic-os,agentic_os/pre_commit/check_code_comments.py) caps comments at 2 contiguous lines / 90 chars after content begins, but exempts the top-of-file header block (every comment line above the first content line,check_code_comments.py:222-224). That carve-out exists for license and teaching headers..kdlis folded into that rule. Inwardcmd/ward-kdl/ward-kdl.fleet.kdlthe first content line isfleet {at line 23, so lines 1-22 are all "header" and fully exempt. Each line is under 90 chars, so the file is technically green - but the license-header exemption is carrying three paragraphs of provenance, sync mechanics, and contract-pin archaeology. That is exactly the durable detail the 2-line rule is meant to push intodocs/behind a short pointer. The rule isn't violated; the exemption is doing something it wasn't designed for, and it recurs across every.kdlin the ward-kdl surface.Decision (Kai)
If a KDL spec needs that much standing context, the context should be first-class data, not a comment header. Full three-layer fix approved.
Design to produce (advisor commission)
1. cli-guard (this repo) - the enabling grammar change. Add a first-class top-level
descriptionnode to the KDL grammar. Neither dialect has one today: fleetconfig (dialect 2,pkg/fleetconfig/fleetconfig.go) nor guardfile (dialect 1,http/guardfile/guardfile.go). The proposal should cover: the node shape (single string vs multi-line block), whether it lands on both dialects or fleetconfig first, how it surfaces (e.g.ward-kdl describe/ reference-doc generation), and grammar-doc updates (docs/kdl-specs.md,docs/fleetconfig.md). It becomes queryable contract data instead of dead comment text.2. ward (
cmd/ward-kdl/) - migration. Move the "what this spec is and does" prose from the comment header into the newdescription. Do not dump the whole header in: the changelog/provenance archaeology (which test pins it, whichmaketarget syncs it,ward#419/ward#415history) is not runtime description - it moves to adocs/*.mdwalkthrough behind a short pointer, per the durable-detail-to-docs convention.descriptioncarries the durable "what/why", docs carry the mechanics.3. agentic-os (
check_code_comments.py) - close the loophole. Tighten the.kdltop-of-file header exemption so a first-classdescriptionis the sanctioned home for standing context and a fat comment header can't silently recur. Sequencing matters: this lands after the grammar field exists and the ward-kdl files migrate, or it breaks currently-green files.Sequencing
cli-guard grammar -> ward-kdl migration -> agentic-os rule tightening. This repo leads because nothing downstream can proceed until the
descriptionnode exists.Authored from the aos director surface after Kai flagged the fleet.kdl header. Config-placement doctrine: the grammar is authored at the engine layer (cli-guard) and flows down.
🔎 ward agent advisor
ward agent advisor --driver clauderan a one-shot standard research pass on this question:Now I have enough grounding in both dialects, the describe/Surface model, the reference-doc generation, and the kdl-go v0.14.1 multi-line string support. Here is the proposal.
Three-layer design: first-class
description, retire the header exemptionGrounded in a fresh clone of cli-guard. Both dialects parse KDL and never evaluate it,
kdl-goisv0.14.1and its tokenizer already supports KDL v2 triple-quoted, auto-dedented multi-line strings (the same"""..."""form the specverbuntilblocks use,docs/specverb-actions.md). That means the enabling primitive already exists in the parser. What is missing is a node that mounts it.Layer 1 - cli-guard grammar (this repo, leads)
Node shape: one scalar node, not a block
Add a single top-level
descriptionnode carrying one string argument, authored as either a bare string or a triple-quoted multi-line block scalar:Recommend a scalar node, not a
description { ... }block with child paragraph nodes. A block invites re-inventing prose structure in KDL and re-opens the exact "how much standing context" question the header carried. One string field stays trivially queryable (Fleet.Description,Guardfile.Description), renders straight into help and docs, and the multi-line form already covers the "three paragraphs" case without new grammar.Name it
description, deliberately distinct from the existingdescribeThe guardfile dialect already has a
describenode in two places: grant-body (guardfile.go:638,Grant.Describe) and action-body (:720,Action.Describe), both per-leaf notes that enrich the thin upstream spec and flow into per-verb help. Reusing that word at the top level would overload it. Keep the split legible:describe- a per-leaf note (existing, unchanged).description- the whole-spec lede (new, top-level).Land on both dialects in this one change, not fleetconfig-first
The issue floats "fleetconfig first." Recommend against it. Layer 3 (the code-comments rule) applies to every
.kdlfile regardless of dialect - the hook does not know fleetconfig from guardfile. If only fleetconfig gets the sanctioned home, Layer 3 can only safely tighten for fleetconfig files, and guardfile.kdlheaders either keep the loophole or break with nowhere to migrate to. The grammar delta is tiny and symmetric, so do both together and let Layer 3 close cleanly across the whole surface.fleetconfig (
pkg/fleetconfig/fleetconfig.go) - document-level node (sibling offleet/director), matching where the comment header sits today (abovefleet {):Description stringtoFleet(nearSchemaVersion).ParseSource's top-levelswitch n.Name()(:140), addcase "description":singleStringArg, reject a duplicate (mirror theseenFleetpattern with aseenDescriptionflag).EmbeddedandOperatorLocalsources. It is pure prose, carries no grant, and is not embed-only, so it must not trip theOperatorLocalrejection thatfleetgets. It sits outside thefleetblock, so it does not disturb theschema-version/agent-required invariants.permissionTokens- it is descriptive by construction.guardfile (
http/guardfile/guardfile.go) - child of the top-levelwrapnode (guardfile puts everything underwrap,:221):Description stringtoGuardfile(:180).applyNode'sswitch(:245), addcase "description":singleArg, reject a duplicate. It is a wrap-body sibling ofspec/base-url/auth.Surfacing: make it the lede, so it is queryable contract data, not dead text
This is the payoff line from the issue - "queryable contract data instead of dead comment text." Wire it into the model each dialect already exposes:
specverb.Describe(Config)builds theSurface(docs/specverb-describe.md). Add a top-levelDescriptionfield toSurface, populated fromGuardfile.Description, and render it as the opening paragraph ofSurface.Markdown(). That single addition flows into all three consumer surfaces at once: the reference docgen/lockwrites (<name>.md), the top-level--help, and the describe verb. The spec's what/why becomes the doc's first paragraph instead of a comment nobody parses.ward-kdl describe(ward repo) printsFleet.Descriptionas the header of its output. Same idea, thinner surface.Doc updates (this repo)
docs/fleetconfig.md- adddescriptionto the Schema bullet list and to the fenced example, abovefleet {.docs/kdl-specs.md- notedescriptionas the shared top-of-spec node in the grammar overview.docs/specverb-describe.md- document the newSurface.Descriptionand that it seeds the reference doc's lede.docs/specverb.md- if it carries a wrap-body node table, add the row.Layer 2 - ward-kdl migration (
cmd/ward-kdl/, second)Worked against
ward-kdl.fleet.kdl(the 22-line header,fleet {at line 23). Split the header by the durable-what/why vs mechanics line:Into
description(the runtime what/why): what this fleet spec is and does - it names the agent roster and each agent's launch shape, it is embedded into the ward binary, it is config not a guarded surface. The standing "what am I looking at" that a reader orward-kdl describeshould always see.To a
docs/*.mdwalkthrough behind a one-line pointer (the archaeology): the provenance and sync mechanics - which test pins the file, whichmaketarget regenerates/syncs it, and theward#419/ward#415contract-pin history. None of that is runtime description, it is changelog. Put it in a newdocs/ward-kdl-fleet.md(ward repo) and leave a single top-of-file comment pointer:That one pointer line is the whole surviving header, well inside any tightened cap. Apply the same split to every other
.kdlin the ward-kdl surface in the same migration, since the issue notes the fat-header pattern recurs across all of them.Layer 3 - agentic-os
check_code_comments.py(last)The exemption today (per the issue,
check_code_comments.py:222-224) waives every comment line above the first content line. For.kdl, the sanctioned home now exists, so the header no longer needs to carry standing context.Recommend: drop the top-of-file header exemption for
.kdlentirely - apply the same body rule (2 contiguous comment lines / 90 chars) to the whole file including the top. KDL specs are config, not source, so they carry no SPDX/license block that would need a multi-line header, and a one-line docs pointer like the migration leaves fits comfortably in the 2-line cap. Dropping the carve-out (rather than shrinking it to a fixed N-line budget) is the strongest anti-recurrence: there is no residual "header allowance" for prose to silently regrow into. A shrunk-but-nonzero budget would just invite the next fat header up to the new limit.Mechanically, gate the exemption on extension: keep the header carve-out for the license/teaching-header file types it was built for, and make
.kdlfall through to the standard contiguous-comment check. If a license line is ever genuinely needed on a.kdl, it still passes under the 2-line cap.Sequencing - confirmed, and why nothing green breaks
The issue's order is correct and is a hard dependency chain, not a preference:
descriptionon both dialects. Until this lands, migrated files have nowhere to put the prose.description, archaeology into docs. After this, no.kdlin the ward-kdl surface relies on a fat header..kdlheader exemption.The break risk is real and one-directional: the instant Layer 3 lands,
ward-kdl.fleet.kdl's 22-line header stops being exempt and the file goes red. So Layer 3 must not merge before Layer 2 has landed on canonical main. Concretely, gate the Layer 3 PR on:pre-commit run check-code-comments --all-filesclean across the ward-kdl surface with the migrated files in place. Run that check against a tree that has both the new rule and the migrated.kdlfiles before merging Layer 3 - if it is green there, no currently-green file regresses in production.One cross-repo caveat worth stating in the Layer 3 PR: the rule ships from agentic-os and rolls to the fleet via ansible, not at install time. Any
.kdlin a repo that has not yet migrated its headers will go red when the tightened hook reaches it. Layer 2 covers the ward-kdl surface, which is the known population, but a quickgrep -rlfor fat-header.kdlfiles elsewhere before Layer 3 rolls out avoids a surprise red in a repo nobody was thinking about.Researched and posted automatically by
ward agent advisor --driver claude(ward#179). This is one-shot research, not a carried change - verify before acting on it.— Claude (she/her), via
ward agentCleared consult→headless (Kai, director session 2026-07-07). No open decision: the fix was approved on filing and the advisor already posted a complete three-layer design. Dispatching Layer 1 only (cli-guard grammar): the scalar top-level
descriptionnode on BOTH dialects (fleetconfig + guardfile) per the advisor's recommendation, surfaced via Surface/describe + reference-doc gen, plus the grammar-doc updates. Layers 2 (ward-kdl migration) and 3 (aos code-comments rule) are sequenced follow-ups dispatched AFTER Layer 1 lands on main - they cannot land before it (grammar→migration→rule is a hard chain, Layer 3 reddens green files until Layer 2 is on main).🔒 Reserved by
ward agent --driver claude— containerengineer-claude-cli-guard-179on hostKAI-DESKTOP-TOWERis carrying this issue (reserved 2026-07-07T22:46:14Z). Concurrentward agentruns are blocked until it finishes or the reservation goes stale (2h0m0s TTL);--forceoverrides.Do not comment on or edit this issue to steer the run while it is reserved. The engineer seeded the body once at launch and never re-reads it, so a comment or edit reaches only human readers, never the running engineer. A correction goes to a new issue, dispatched fresh — that is the only channel that reaches a run in flight. Where the forge supports it, ward locks this conversation to make that a road-block rather than a convention (ward#494).
run seed context — what this run is carrying (ward#609)
coilyco-flight-deck/cli-guard#179· branchissue-179· driverclaude· workflowdirect-mainengineer-claude-cli-guard-179· wardv0.422.0· dispatched2026-07-07T22:46:14ZIssue body as seeded:
… (truncated to 2000 chars; full body is on this issue)
Static container doctrine and seed boilerplate are identical every run and omitted here (they ride ward v0.422.0).
— Claude (she/her), via
ward agentWARD-OUTCOME: done - top-level
descriptionnode added to all three .kdl dialects (fleetconfig, http/guardfile, exec), surfaced in describe + reference docs, landed on main.The grammar change itself was small and clean - a single string arg parsed into a typed field, fail-closed on empty. The interesting decision was scope: the issue named two dialects, but the repo actually has three .kdl-producing grammars (fleetconfig, the http/guardfile
wrap, and the exec-dialectwrap), and all three carry fat headers in ward-kdl. Since Layer 3's whole point is to drop the header exemption, leaving the exec dialect out would just move the loophole rather than close it, so I did all three. That felt like the right read of the intent even though it's slightly beyond the letter.What fought back was not the code - it was the docs. FEATURES.md, fleetconfig.md, and kdl-specs.md were each sitting at ~3990/4000 chars against a hard documentation-layout cap, so adding even a one-line pointer overflowed all three. I moved the real grammar walkthrough into its own docs/kdl-description.md and then spent more effort than I'd like trimming existing prose word-by-word to fit the pointers back under the cap. It works and reads fine, but it's a smell that those three docs are permanently one sentence away from the ceiling.
Confidence is high: full pre-commit suite green (including the very code-comments hook this issue is about), godoc pinned, tests cover parse + surface + fail-closed for each dialect. Follow-ups are the already-sequenced Layers 2 (ward-kdl migration) and 3 (agentic-os rule tightening) - both blocked on this being on main, which it now is. One minor thing worth watching: the opcore inline source (ParseInline, ward-mcp) shares guardfile pieces but is embedded in a larger doc, so it deliberately got no top-level description; if that grammar ever grows standalone .kdl files, it'll want the same node.