Skip to content

Make sure AGENTS.md template is synced & polishes - #13

Merged
xen0n merged 44 commits into
liyi-run:mainfrom
xen0n:template-sync-and-polishes
Sep 19, 2026
Merged

xen0n merged 44 commits into
liyi-run:mainfrom
xen0n:template-sync-and-polishes

Conversation

@xen0n

@xen0n xen0n commented Sep 19, 2026

Copy link
Copy Markdown
Contributor

No description provided.

Separate two claims that were both being carried by `=trivial`: a *risk*
claim ("too small to hide behavior") and an *epistemic* claim ("intent is
intrinsic to the surface form"). In practice `=trivial` was stretched onto
substantial code — an 11-line context struct, a 163-line test module —
where the item is not small but its intent is self-evident. This is the
review-action a reviewer actually wanted but had no label for.

Add `=self-doc` for the epistemic case, with a strict test that resists the
"self-documenting" excuse: a competent-but-domain-stranger reader holding
only name, signature, and durable co-located context must recover every
property any observer could rely on, including non-value-domain effects
(timing, resources, termination, information flow). The test is an open
observer counterfactual, not a finite effect checklist, and side effects
are presumptively disqualifying with a discharge (not assert) obligation.

Also document the derivative-item rule: items whose whole purpose is to
exercise/assert another specced item (chiefly tests) carry no independent
intent and should be excluded from inference entirely — no entry, not a
lighter sentinel — since specifying test intent is circular with adversarial
test generation.

Record the threat-model boundary as an explicit non-goal: 立意 surfaces
hidden intent but does not verify the honesty of intent deliberately
misrepresented via non-co-located or runtime-controlled state.

Original prompt:

> [multi-turn discussion, see session] Assess integrating 立意 into agentic
> scaffolds; then: whether `=per-se`/`=self-doc` should complement `=trivial`;
> survey the repo's `=trivial` uses; settle the reference reader, durable
> context, observer/effect closure, and threat-model boundary; then make each
> change to the spec, the AGENTS.md edits, and the implementation, in commits.

AI-assisted-by: Claude Opus 4.8 (OpenCode)
Signed-off-by: WANG Xuerui <[email protected]>
Update the AGENTS.md intent-spec template (the deliverable downstream repos
adopt) to match the design doc. Rule 1 gains the `=self-doc` sentinel with
its strict test — a competent-but-domain-stranger reader, durable co-located
context only, an open observer counterfactual over non-value-domain effects,
side effects presumptively disqualifying, and a discharge (not assert)
obligation. Rule 8 gains the derivative-item exclusion (no entry for items
whose whole purpose is to exercise another specced item) and extends the
`@liyi:nontrivial` conflict to `=self-doc`. The embedded schema `intent`
description lists the new sentinel.

Original prompt:

> [multi-turn discussion, see session] ... then make each change to the spec,
> the AGENTS.md edits, and the implementation, in commits.

AI-assisted-by: Claude Opus 4.8 (OpenCode)
Signed-off-by: WANG Xuerui <[email protected]>
Implement the =self-doc sentinel defined in the design doc and template.
check_trivial_ignore now recognizes both "=trivial" and "=self-doc" in the
sidecar: each emits an info-level Trivial diagnostic, is not flagged stale,
is not counted unreviewed on its own, and is skipped by adversarial testing;
an in-span @liyi:nontrivial conflicts with either (ConflictingTriviality
error), which is what forbids a side-effecting item from being filed as
self-documenting. format_github_actions treats =self-doc as a sentinel so its
value is not appended as intent prose. The schema intent description lists the
new sentinel. A self_doc_sidecar fixture and golden test cover the three
cases: a plain enum, a non-trivial-but-self-doc recursive helper (the case
=trivial could not honestly cover), and a @liyi:nontrivial conflict on
side-effecting remainder logic.

The check.rs and diagnostics.rs behavior changes touch two reviewed items
(check_trivial_ignore, format_github_actions); their intent text is updated to
mention =self-doc, "reviewed" is unset, and source_hash dropped per the
staleness rules, leaving them for human re-approval.

Original prompt:

> [multi-turn discussion, see session] ... then make each change to the spec,
> the AGENTS.md edits, and the implementation, in commits.

AI-assisted-by: Claude Opus 4.8 (OpenCode)
Signed-off-by: WANG Xuerui <[email protected]>
The =self-doc sentinel (added in 5d95f88 and the follow-up commits
afb0a8e, f19e585, 38f207b) separates two claims that =trivial previously
conflated: a risk claim ("too small to hide behavior") from an epistemic
claim ("intent is intrinsic to the surface form"). Nine pre-existing
=trivial entries had been stretched onto substantial items — data structs
and a context bundle, an enum, and multi-line search helpers — where
=trivial was a lie about size and =self-doc is the honest label. Each
affected spec sets "reviewed" to false (intent changed, source unchanged),
leaving it for human re-approval per the staleness rules.

Genuinely tiny items keep =trivial: one-line consts/statics, a one-line
function body, and the declarative CI trigger block. Derivative test specs
are dropped in a follow-up commit.

Original prompt:

> please survey the pre-existing `=trivial` intents and change to `=self-doc` or re-infer each, for any such intent no longer conforming to the current revised standard (read `git diff 5d95f88` for the exact changes).

> please commit.

AI-assisted-by: DeepSeek V4.1 Flash (OpenCode)
Signed-off-by: WANG Xuerui <[email protected]>
The revised standard excludes derivative items from inference entirely: an
item whose whole purpose is to exercise or assert an already-specced item
adds no invariant of its own, so a per-item entry creates a fake review
burden, a hash-tracking liability, and circularity (adversarial tests are
generated from reviewed intent). Remove the sidecar entries for the
context.rs `mod tests` module, its `write` helper, and all nine test
functions; the end state for a derivative item is no entry, not a lighter
=trivial or =self-doc row.

Original prompt:

> please survey the pre-existing `=trivial` intents and change to `=self-doc` or re-infer each, for any such intent no longer conforming to the current revised standard (read `git diff 5d95f88` for the exact changes).

> please commit.

AI-assisted-by: DeepSeek V4.1 Flash (OpenCode)
Signed-off-by: WANG Xuerui <[email protected]>
Two "Linter behavior" paragraphs claimed sidecar `=trivial`/`=self-doc`
specs are "not stale" and "not counted as unreviewed". Both claims are
false: `check_item_hash` and `check_review_status` have no sentinel case,
so a sentinel spec is an ordinary tracked spec — unsetting `reviewed`
surfaces it as unreviewed, and a source change marks it stale. The rest of
the design already agrees (the reviewer-responsibility table gives a review
action per sentinel, `=doc` is explicitly staleness-tracked, and the level
`all` summary lists trivial/unreviewed/stale together). Correct the doc,
not the code: everything gets reviewed, sentinels included.

Original prompt:

> actually isn't the point "every intent should get reviewed" i.e. "trivial/self-doc sentinels should be reviewed too"? the doc has a table describing reviewer responsibilities -- what to prove when encountering different sentinels, so the implication has always been that they are to human-approve EVERYTHING. so any text implying otherwise should get corrected. and regarding triviality of "single expressions", maybe modify to "simple expressions", to disqualify complex ones like `machine == EM_FOO && (bar_count <= X || should_baz && quux (state->xxx, yyy) && ...`. what do you think?

AI-assisted-by: DeepSeek V4.1 Flash (OpenCode)
Signed-off-by: WANG Xuerui <[email protected]>
"=trivial" is a risk claim (too small to hide behavior), but the docs
defined the boundary only as "simple getters, one-line wrappers" — which
conflates size with syntax. A body that is a single expression is not
automatically trivial: a compound boolean guard whose operand order,
short-circuit evaluation, nested calls, or cost are load-bearing hides
real behavior and belongs in prose. State that a body must be a *simple*
expression, not merely one expression, and note that body line count is a
heuristic rather than the criterion. Also fix the `@liyi:trivial` marker
definition, which described the =self-doc epistemic claim ("intent is
self-evident from its signature") rather than the =trivial risk claim.

Original prompt:

> actually isn't the point "every intent should get reviewed" i.e. "trivial/self-doc sentinels should be reviewed too"? the doc has a table describing reviewer responsibilities -- what to prove when encountering different sentinels, so the implication has always been that they are to human-approve EVERYTHING. so any text implying otherwise should get corrected. and regarding triviality of "single expressions", maybe modify to "simple expressions", to disqualify complex ones like `machine == EM_FOO && (bar_count <= X || should_baz && quux (state->xxx, yyy) && ...`. what do you think?

AI-assisted-by: DeepSeek V4.1 Flash (OpenCode)
Signed-off-by: WANG Xuerui <[email protected]>
The =self-doc section cited "the 163-line test module" as its canonical
example of a substantial-but-self-evident item. That contradicts the
derivative-item rule added in the same change: a test whose whole purpose
is to exercise or assert an already-specced item gets no sidecar entry at
all, so it is never filed as =self-doc. Replace the example with a
data-only enum (RecoveryMethod), which =self-doc legitimately covers.

Original prompt:

> actually isn't the point "every intent should get reviewed" i.e. "trivial/self-doc sentinels should be reviewed too"? the doc has a table describing reviewer responsibilities -- what to prove when encountering different sentinels, so the implication has always been that they are to human-approve EVERYTHING. so any text implying otherwise should get corrected. and regarding triviality of "single expressions", maybe modify to "simple expressions", to disqualify complex ones like `machine == EM_FOO && (bar_count <= X || should_baz && quux (state->xxx, yyy) && ...`. what do you think?

AI-assisted-by: DeepSeek V4.1 Flash (OpenCode)
Signed-off-by: WANG Xuerui <[email protected]>
Rule 6 authorized adversarial test generation from any item with
`"reviewed": true`. Once sentinel specs are properly reviewed (they are
ordinary tracked specs — only their attack surface differs), that clause
would sweep `=trivial`/`=self-doc` items into test generation, contradicting
the design's "skipped by the adversarial testing agent". State the
exclusion explicitly: sentinels carry no agent-authored intent for a second
model to attack. Applied to the AGENTS.md template and the design doc's
minimal-instruction mirror.

Original prompt:

> actually isn't the point "every intent should get reviewed" i.e. "trivial/self-doc sentinels should be reviewed too"? the doc has a table describing reviewer responsibilities -- what to prove when encountering different sentinels, so the implication has always been that they are to human-approve EVERYTHING. so any text implying otherwise should get corrected. and regarding triviality of "single expressions", maybe modify to "simple expressions", to disqualify complex ones like `machine == EM_FOO && (bar_count <= X || should_baz && quux (state->xxx, yyy) && ...`. what do you think?

AI-assisted-by: DeepSeek V4.1 Flash (OpenCode)
Signed-off-by: WANG Xuerui <[email protected]>
Agents can hit genuine conflicts between normative rules (or ambiguities
the convention leaves open) where guessing silently produces the wrong
deliverable. Add an explicit escalation preference: name the conflict,
don't guess; ask once with candidate resolutions if a human is reachable;
otherwise take the conservative reading, leave affected specs unreviewed,
and surface the conflict. Scope it so it is not a license to stall on
judgment the standard delegates (e.g. the =self-doc observer test).

Added to the AGENTS.md template and the design doc's agent-skill section,
both after the tracked requirement blocks.

Original prompt:

> okay, and do you think it would be worth it to add conflict resolution preference (when several liyi agent rules appear to be in conflict, don't ever guess, ask the user) and whatever else to the AGENTS.md instructions, per your experience in this session?

> ok, make the changes, batched by semantic change i.e. each commit makes one semantic change to the docs & related code. the conflict rule belongs to its own commit as well.

AI-assisted-by: DeepSeek V4.1 Flash (OpenCode)
Signed-off-by: WANG Xuerui <[email protected]>
Rule 10 told agents that after unsetting `reviewed` and deleting
`source_hash`, the human would run `liyi check --fix` or
`liyi approve --rehash`. There is no `--rehash` flag: the `approve`
subcommand only defines `--yes`, `--dry-run`, `--item`,
`--unreviewed-only`, `--stale-only`, and `--req-only`, and the design
doc's `liyi approve` flag list has no such option either. `liyi approve`
already rehashes a stale-reviewed item when the human confirms it, so
drop the bogus flag and point at the real command.

Root cause: the never-compute-hashes paragraph was written directly into
AGENTS.md without a matching design-doc review, so the invented flag was
never checked against the CLI.

Original prompt:

> check if the current `liyi:template` section in `AGENTS.md` differs
> from `liyi-design.md` prescription, fix `AGENTS.md` and commit if any
> inconsistency is found.

> Bidirectional sync -- fix `--rehash` in a commit, then update the
> design doc in another syncing commit. I was implying the design doc
> leads AGENTS.md but apparently this is false -- fixing the trailing doc
> is the right call.

AI-assisted-by: DeepSeek V4.1 Flash (OpenCode)
Signed-off-by: WANG Xuerui <[email protected]>
The design doc reproduces the AGENTS.md behavioral rules and carries the
canonical `.liyi.jsonc` JSON Schema, but both had fallen behind the
template:

- Reproduced rules 1, 2, 8, 10, and 11 were the pre-`=self-doc` text.
  Rule 1 lacked the `=self-doc` sentinel and its observer test plus the
  reviewed-unset warning; rule 8 lacked the `@liyi:nontrivial` /
  `"=self-doc"` conflict and the derivative-item exclusion; rule 10
  lacked the never-compute-hashes paragraph; rule 11 still said "consume
  the `--prompt` output" instead of "prefer `--prompt` for structured
  JSON". Rule 2 was the pre-`@liyi:note` v0.2 wording.

- The Appendix schema's `intent` description enumerated only `=doc` and
  `=trivial`, omitting `=self-doc`, so it disagreed with both the
  template's embedded schema and `schema/liyi.schema.json`.

Root cause: later changes landed in AGENTS.md (rule 1/8 in f19e585, the
never-compute-hashes paragraph in c4fb25c, rule 11 in 72db3ff) and in
`schema/liyi.schema.json` (38f207b) without the design doc's reproduced
copies being updated, because only the changelog narrative was treated
as the spec while the reproduced rules and appendix were not.

Original prompt:

> check if the current `liyi:template` section in `AGENTS.md` differs
> from `liyi-design.md` prescription, fix `AGENTS.md` and commit if any
> inconsistency is found.

> Bidirectional sync -- fix `--rehash` in a commit, then update the
> design doc in another syncing commit. I was implying the design doc
> leads AGENTS.md but apparently this is false -- fixing the trailing doc
> is the right call.

AI-assisted-by: DeepSeek V4.1 Flash (OpenCode)
Signed-off-by: WANG Xuerui <[email protected]>
The design doc described the AGENTS.md payload as "10 behavioral rules"
in four places, but the reproduced instruction has eleven rules and the
same document elsewhere says "the 11 behavioral rules" / "the full
11-rule protocol". The count is incidental, so drop the exact numbers
rather than renumber (and rewrite the MUST/SHOULD split's "these 3
things" / "these 8 things" to avoid implying a count).

Original prompt:

> please fix the rule count mismatch (preferably don't spell out exact
> numbers if at all possible), and also the "key principles" -- make the
> subtitle more specific, then sync it to AGENTS.md.

AI-assisted-by: DeepSeek V4.1 Flash (OpenCode)
Signed-off-by: WANG Xuerui <[email protected]>
The design doc's `### Key principles` section (adversarial not
confirmatory, spec is the referee, model diversity, never modify source
logic) had no counterpart in the AGENTS.md template, even though its
sibling `### Resolving rule conflicts` was already synced there.

Rename the heading to `### Key principles of the intent protocol` so it
is specific about what it governs, and copy the section verbatim into
the AGENTS.md template between the behavioral rules and `### Resolving
rule conflicts`, matching the design doc's ordering.

Original prompt:

> please fix the rule count mismatch (preferably don't spell out exact
> numbers if at all possible), and also the "key principles" -- make the
> subtitle more specific, then sync it to AGENTS.md.

AI-assisted-by: DeepSeek V4.1 Flash (OpenCode)
Signed-off-by: WANG Xuerui <[email protected]>
AGENTS.md is the source of truth for the agent instruction — `liyi init`
ships the block between the `<!-- liyi:template:start -->` and
`<!-- liyi:template:end -->` markers — while docs/liyi-design.md
reproduces the behavioral rules, key principles, and the `.liyi.jsonc`
schema appendix. Nothing kept the two copies in sync, so they have
silently drifted before: the `=self-doc` sentinel reached the template
and `schema/liyi.schema.json` but not the design doc's reproduced rules
or appendix.

Add scripts/check-template-sync.py, which extracts the reproduced regions
from both files and fails with a unified diff when they diverge. The
sidecar schema is compared as parsed JSON, so the template's `\u0040`
quine escape and the doc's literal `@` are treated as equal.
"Resolving rule conflicts" is intentionally skipped (the doc states it as
prose, the template as a numbered list) as is the triage schema (no
design-doc counterpart).

Wire the check into `make lint`, and mirror it in `make lint-short` so
the commit gate enforces it.

Original prompt:

> please add that consistency check to both the commit gate and CI!

AI-assisted-by: DeepSeek V4.1 Flash (OpenCode)
Signed-off-by: WANG Xuerui <[email protected]>
List the new agent-template sync check among the `make lint` gates in
AGENTS.md, CONTRIBUTING.md, and both contributing guides (EN/ZH).

Original prompt:

> please add that consistency check to both the commit gate and CI!

AI-assisted-by: DeepSeek V4.1 Flash (OpenCode)
Signed-off-by: WANG Xuerui <[email protected]>
Add a `template-sync` job that runs scripts/check-template-sync.py, and
record the job in the workflow's sidecar so `liyi check` stays green.

Original prompt:

> please add that consistency check to both the commit gate and CI!

AI-assisted-by: DeepSeek V4.1 Flash (OpenCode)
Signed-off-by: WANG Xuerui <[email protected]>
The template-sync check had to parse regions (fenced Markdown block,
headings) and special-case the JSON schema, because the AGENTS.md
template block — behavioral rules, key principles, conflict resolution,
and both JSON schemas — was not byte-identical to the design doc's
condensation: prose conflicts instead of a list, the sidecar schema in a
separate appendix, the triage schema missing, and the marker escaped as
`\u0040` in AGENTS.md.

Make docs/liyi-design.md carry the same `<!-- liyi:template:start/end -->`
block verbatim: replace the inline reproductions with a pointer to the
new section and fold the schema appendix into the block (which also
brings in the previously absent triage schema). Unescape the marker in
AGENTS.md's embedded schema so the blocks match byte-for-byte; it sits in
a fenced code block, which the scanner already skips.

The check is now a plain string comparison, and `--sync` (surfaced as
`make template-sync-fix`) copies the AGENTS.md block over the design
doc's, so drift can be fixed instead of just reported.

Sidecar consequences of the design-doc edit, resolved in the same
commit to keep `liyi check` green: the `version-field-required`
requirement's hash changed (its cross-reference now points at the
verbatim block), and `validate_version`'s related edge was re-filled with
`liyi check --fix`.

Original prompt:

> are you over-engineering the template check? why not reframe the
> design doc so that it also features a `<!-- liyi:template -->` block,
> then we can simply extract, compare and even offer to sync that?

AI-assisted-by: DeepSeek V4.1 Flash (OpenCode)
Signed-off-by: WANG Xuerui <[email protected]>
The `liyi:template` block started with its own `## 立意 (Intent Specs)`
heading, so the design doc could not choose where the block sits without
that heading dictating the outline. Move the heading outside the
`<!-- liyi:template:start/end -->` markers in both files; the block now
begins at "When writing or modifying code:".

`liyi init` must still ship the heading, so `init_agents_md` now prepends
`## 立意 (Intent Specs)` before the block when appending to or creating
AGENTS.md. Its intent was updated accordingly and left unreviewed for a
human to re-approve; the `init_sidecar` span shift was rehashed in the
same commit.

Original prompt:

> Hmm, maybe the block should be *minus the section heading* to preserve
> the design doc's outline. Also in another commit please make the job
> `sync-template` and `check-template-sync` for better readability.

AI-assisted-by: DeepSeek V4.1 Flash (OpenCode)
Signed-off-by: WANG Xuerui <[email protected]>
Rename the gate from `template-sync` to `check-template-sync` and the
fixer from `template-sync-fix` to `sync-template`, matching the
verb-first style of `fmt-check`, `liyi-check`, and the
`check-template-sync.py` script. The CI job (and its sidecar spec) is
renamed to `check-template-sync` as well, and the failure hint now points
at `make sync-template`.

Original prompt:

> Hmm, maybe the block should be *minus the section heading* to preserve
> the design doc's outline. Also in another commit please make the job
> `sync-template` and `check-template-sync` for better readability.

AI-assisted-by: DeepSeek V4.1 Flash (OpenCode)
Signed-off-by: WANG Xuerui <[email protected]>
Reconsider the previous commit's approach: instead of stripping the
section heading out of both blocks and teaching `liyi init` to re-add it,
keep the `## 立意 (Intent Specs)` heading inside AGENTS.md's template
block — the artifact `liyi init` actually ships — and leave only the
design doc's block heading-less, so the design doc's own heading still
governs its outline.

This reverts the `init_agents_md` special-casing (and its sidecar churn):
`agents_md_block` is a pure extractor again and the shipped block stays
self-contained. The template-sync check now accounts for the heading by
comparing AGENTS.md's block against the design doc's block prefixed with
`## 立意 (Intent Specs)`, and `make sync-template` materializes AGENTS.md
from the design doc, adding the heading — the design doc is the source.

Original prompt:

> Oh, actually I meant: when syncing the template block from the design
> doc to `AGENTS.md`, add the desired section heading in this step, so the
> `liyi init` logic stays the same. When checking sync status, account for
> the section heading handling. Do you think this to be better?

AI-assisted-by: DeepSeek V4.1 Flash (OpenCode)
Signed-off-by: WANG Xuerui <[email protected]>
This commit was authored entirely manually by me, and the sidecar
for `scripts/check-template-sync.py` is also manually crafted
(`liyi init` didn't surface the top-level constant I desired).

Signed-off-by: WANG Xuerui <[email protected]>
`agents_md_block_extracts_valid_template` still expected the old
`## 立意` section heading, but the heading was reworded to
`## The 立意 (Intent Specs) design pattern for agents`. Update the
assertion so the test matches the extracted block again.

This is a pre-existing failure left by the manual heading refactor; it is
unrelated to the Python tree_path work but blocks `make verify`.

Original prompt:

> Please check `git diff --cached` and add test cases for the added
> items, and commit.

AI-assisted-by: DeepSeek V4.1 Flash (OpenCode)
Signed-off-by: WANG Xuerui <[email protected]>
The `@liyi:related` marker for HEADING was written inside a triple-quoted
string, so the scanner read the closing `'''` as part of the requirement
name (`agents-md-instructions-section-naming'''`), and `liyi check`
failed with "related requirement not found". Make it a plain comment and
correct the sidecar's related edge, then let `liyi check --fix` rehash.

This is a pre-existing failure from the manually crafted sidecar; it is
unrelated to the Python tree_path work but blocks `make verify`.

Original prompt:

> Please check `git diff --cached` and add test cases for the added
> items, and commit.

AI-assisted-by: DeepSeek V4.1 Flash (OpenCode)
Signed-off-by: WANG Xuerui <[email protected]>
Python's tree_path config only mapped functions and classes, so
module-level constants were invisible to `liyi init` and to the resolver.
Register `const` for module-level `assignment` nodes, extract the name
via a `python_node_name` callback that requires a plain-identifier target
directly under the module root (excluding locals and attributes), and
traverse the wrapping `expression_statement` as a transparent kind.

Add resolve/compute/roundtrip tests for the newly sampled
`TOPLEVEL_CONST`, plus a discovery test pinning that only module-level
assignments become `const` items.

Original prompt:

> Please check `git diff --cached` and add test cases for the added
> items, and commit.

AI-assisted-by: DeepSeek V4.1 Flash (OpenCode)
Signed-off-by: WANG Xuerui <[email protected]>
Replace the bare `<!-- liyi:template:start/end -->` markers with a
DocToc-style pragma that names the region and carries a monotonic
revision, so a downstream `AGENTS.md` can be refreshed in place by
`liyi migrate` instead of relying on `init_agents_md` matching the
section heading:

    <!-- START liyi agent instructions rev. 1 -->
    <!-- DON'T EDIT THIS SECTION. IT IS UPDATED ONLY BY `liyi migrate`, FROM A NEWER LIYI RELEASE. -->
    ...
    <!-- END liyi agent instructions -->

Unlike DocToc — whose reminder tells users to re-run it after every
outline change — this block changes only when a newer Liyi release ships
a different template, so the reminder says so explicitly.

Docs: the `agents-md-instructions-section-naming` requirement now defines
the pragma, and the `liyi migrate` behavior describes refreshing the
`AGENTS.md` region.

The pragma literal is shared by the design-doc appendix, `AGENTS.md`,
`check-template-sync.py`, and `init.rs`, so this commit carries the
mechanical constant/message/docstring updates to keep extraction working.
Behavior changes follow: `liyi init` shipping the pragma, the prefix-based
guard, and the `liyi migrate` Markdown path.

Sidecar churn: the requirement was rehashed and the script's `HEADING`
related edge re-acknowledged; `TEMPLATE_START`, `TEMPLATE_END`, and
`agents_md_block` were re-opened for review.

Original prompt:

> let's use the legacy style but let's make it `START Liyi agent
> instructions rev. 1` and `END liyi agent instructions`, that feels
> better doesn't it?

> Actually this block updates much less frequently than DocToc's case --
> only a Liyi release could possibly bump that, and not for every Liyi
> release, while a DocToc is to be refreshed after every document outline
> change by their user. Let's reword the reminder and start with doc
> changes then implementation.

AI-assisted-by: DeepSeek V4.1 Flash (OpenCode)
Signed-off-by: WANG Xuerui <[email protected]>
Swap the verbose two-clause reminder for the 78-column form, so the
pragma stays readable without wrapping. Used `FROM` rather than a second
`WITH` ("REFRESH WITH `liyi migrate` FROM A NEWER LIYI"), and reworded the
`agents-md-instructions-section-naming` requirement's reference to match
(`BLOCK`, not `SECTION`).

Sidecar churn in the same commit: `TEMPLATE_START` rehashed and the
script's `HEADING` related edge re-acknowledged.

Original prompt:

> Let's agree on a shorter wording, ideally below 80 columns, I'd suggest:
>
> ```markdown
> <!-- DON'T EDIT THIS BLOCK. REFRESH WITH `liyi migrate` WITH A NEWER LIYI. -->
> ```
>
> This is 78 characters. Is this better?

AI-assisted-by: DeepSeek V4.1 Flash (OpenCode)
Signed-off-by: WANG Xuerui <[email protected]>
…init

The portable agent instruction block is now wrapped in a DocToc-style,
version-tracked pragma instead of internal markers that `liyi init`
stripped:

    <!-- START liyi agent instructions rev. N -->
    <!-- DON'T EDIT THIS BLOCK. REFRESH WITH `liyi migrate` FROM A NEWER LIYI. -->
    ...
    <!-- END liyi agent instructions -->

- `TEMPLATE_REVISION` is the single source for the current revision;
  `agents_md_block()` re-renders the block from the repo's AGENTS.md at
  that revision, pragma, reminder, and end marker included.
- `find_agents_md_block()` locates a block in arbitrary text without
  inspecting the revision, so a block written by an older `liyi` is found.
- `migrate_agents_md()` rewrites a file's block to `TEMPLATE_REVISION`,
  preserving surrounding content and skipping the write when already
  current (idempotent).
- `init_agents_md()` now keys off the pragma prefix rather than the
  `## 立意` heading — which also fixes the duplicate-append bug where a
  reworded heading made the guard miss an existing block. `--force`
  replaces the existing block instead of appending a second one.

Sidecar churn: init.rs specs were re-generated for the new constants and
functions; `init_agents_md`'s intent was rewritten and left unreviewed.

Original prompt:

> let's go.

AI-assisted-by: DeepSeek V4.1 Flash (OpenCode)
Signed-off-by: WANG Xuerui <[email protected]>
`liyi migrate` previously walked only `.liyi.jsonc` sidecars. It now also
refreshes the versioned agent-instruction block in an `AGENTS.md`:

- `resolve_agents_md_targets()` finds files carrying the
  `START liyi agent instructions` pragma: in directory mode it collects
  files named `AGENTS.md`, and an explicitly-passed file is accepted
  wherever it lives so a repository's preferred location can be migrated
  directly. Targeting `AGENTS.md` by name keeps the design doc's
  heading-less source block from ever being rewritten.
- The CLI dispatches sidecar targets to schema migration and agent files
  to `migrate_agents_md()`, and succeeds when either kind is found
  (instead of requiring a sidecar).

Covered by new discovery unit tests and CLI tests for a stale block, a
directory containing both kinds of target, and init idempotency.

Original prompt:

> let's go.

AI-assisted-by: DeepSeek V4.1 Flash (OpenCode)
Signed-off-by: WANG Xuerui <[email protected]>
Signed-off-by: WANG Xuerui <[email protected]>
Close the gaps left by the pragma work.

Unit (`init.rs`):
- create ships a pragma-wrapped block (pragma, reminder, closing marker,
  exactly one block),
- an existing file without a block gets one appended, prefix preserved,
- an existing block is refused without `--force` and replaced in place
  with `--force` (no duplicate).

End-to-end (`liyi-cli/tests/cli.rs`, via the compiled binary):
- `init` output now also asserts the reminder and closing marker,
- `init --force` replaces a stale `rev. 0` block with `rev. 1` and leaves
  exactly one block.

Original prompt:

> Okay add the unit tests and E2E tests, one commit for each feature.

AI-assisted-by: DeepSeek V4.1 Flash (OpenCode)
Signed-off-by: WANG Xuerui <[email protected]>
Close the remaining gaps for the migrate feature.

Unit (`init.rs`, `discovery.rs`):
- `migrate_agents_md` preserves the content before and after the block,
- a directory walk ignores a pragma-carrying file that is not named
  `AGENTS.md` (the design doc's heading-less source is never targeted).

End-to-end (`liyi-cli/tests/cli.rs`, via the compiled binary):
- `migrate` is idempotent (a second run leaves the file byte-identical),
- a directory containing only an `AGENTS.md` still succeeds,
- an explicitly-passed file at a custom location is migrated,
- `migrate .` updates `AGENTS.md` but leaves a heading-less
  `liyi-design.md`-style block untouched.

Original prompt:

> Okay add the unit tests and E2E tests, one commit for each feature.

AI-assisted-by: DeepSeek V4.1 Flash (OpenCode)
Signed-off-by: WANG Xuerui <[email protected]>
A directory argument to `liyi migrate` used to walk the whole subtree for
`AGENTS.md` files. In a monorepo that cannot tell own-controlled
subtrees (`subprojects/*`) from ungoverned ones (`3rdparty/*`,
`vendor/*`), so recursive migration could rewrite another project's
agent instruction block to our revision.

Make discovery strictly mirror `liyi init`: a directory argument
contributes exactly its own `AGENTS.md` and never walks. Moving through a
tree is now the caller's explicit choice, one governed directory at a
time, and a file argument still migrates exactly that file for a
preferred location. Both commands now share `AGENTS_MD_FILENAME`, so they
cannot disagree about which file they manage.

Covered by a unit test (nested `AGENTS.md` is reachable only by naming
its directory) and an end-to-end test (`migrate .` leaves
`3rdparty/vendored/AGENTS.md` byte-identical).

Original prompt:

> I actually expect strict equivalence because we're not in a position to
> recursively update other projects not under our governance, and right
> now we lack the ability to sketch out that boundary in a monorepo. Say
> repo root and `subprojects/*` are own-controlled but `3rdparty/*` and
> `vendor/*` are NOT.

AI-assisted-by: DeepSeek V4.1 Flash (OpenCode)
Signed-off-by: WANG Xuerui <[email protected]>
Recursive `liyi migrate` (sidecars) and `liyi check` / `--fix` cannot tell
owned subtrees from vendored ones (`3rdparty/`, `vendor/`), so they can
rewrite another project's sidecars or agent instructions. Agent-file
migration is already non-recursive (3f451fcc38f7), but sidecars and
`check` are not, and `.liyiignore` expresses the boundary as an *exclude*
list where an *include* concept is wanted.

Capture this as a backlog document with the current state, open design
questions (include list vs. root markers vs. workspace manifest; which
operations to scope; nested VCS boundaries), and a minimum viable
direction. No requirement is declared yet.

Original prompt:

> please document this gap somewhere, as a backlog for now... the
> `next-steps.md` might not be a good location specifically, you may want
> a new file.

AI-assisted-by: DeepSeek V4.1 Flash (OpenCode)
Signed-off-by: WANG Xuerui <[email protected]>
立意 writes its agent instructions to `AGENTS.md`, and any harness that
reads `AGENTS.md` works out of the box. Make that an explicit,
advertised commitment by adding a "Harness compatibility" section to
both READMEs.

Rather than special-casing vendor instruction files (e.g. Claude Code's
`CLAUDE.md`), the README now follows the AGENTS.md convention itself: a
harness that does not read `AGENTS.md` is the adopter's responsibility
to bridge by placing the block where that harness will read it. This
keeps one neutral instruction file instead of a per-harness matrix, and
it matches the convention's own backward-compatibility guidance.

Original prompt:

> Can we sidestep this and call out the discrepancy loud by announcing
> somewhere that we explicitly adhere to the https://agents.md
> convention?

> let's avoid singling out Claude Code and simply claim "If your harness
> doesn't read AGENTS.md, it is your responsibility to find another
> suitable place for the instruction block and make sure it is being
> read by your harness", likewise for the `liyi init` prompt. The latter
> can be terse because it's CLI output and the reader might be an LLM
> (recent Claude models can be expected to know that and adapt for the
> user). No need to mention "liyi commands stay single-file".

> "harness" and "harness engineering" tend to be translated as
> "驾驭框架/驾驭工程" in zh_CN, not "智能体工具" (that's just "agentic
> tools"). fix that and we're good to commit.

AI-assisted-by: DeepSeek V4.1 Flash (OpenCode)
Signed-off-by: WANG Xuerui <[email protected]>
`liyi init` now prints a one-line note after scaffolding `AGENTS.md`,
stating that the block follows the AGENTS.md convention and that a
harness which does not read `AGENTS.md` is responsible for placing the
block where it will be read. The note is unconditional -- no detection,
no vendor filename in the code -- so an agent-driven adoption gets the
rule at the moment it matters.

Editing inside `main()` shifted its span and changed its hash, so the
main.rs sidecar is re-anchored and `main` is left `"reviewed": false`
for human re-confirmation.

Original prompt:

> let's avoid singling out Claude Code and simply claim "If your harness
> doesn't read AGENTS.md, it is your responsibility to find another
> suitable place for the instruction block and make sure it is being
> read by your harness", likewise for the `liyi init` prompt. The latter
> can be terse because it's CLI output and the reader might be an LLM
> (recent Claude models can be expected to know that and adapt for the
> user). No need to mention "liyi commands stay single-file".

AI-assisted-by: DeepSeek V4.1 Flash (OpenCode)
Signed-off-by: WANG Xuerui <[email protected]>
Signed-off-by: WANG Xuerui <[email protected]>
Remove the completed Tier 1 section and the finished 2A (approval
follow-through), 2B (LSP library refactor), and 2F (note context)
sections so the backlog lists only outstanding work. Drop the
now-dangling reference to item 1.3.

Original prompt:

> Let's remove the already-done parts from it, refresh the stale info and
> drop the explicit mention of number of untracked requirements, and
> finally update the banner, in 3 commits.

AI-assisted-by: DeepSeek V4.1 Flash (OpenCode)
Signed-off-by: WANG Xuerui <[email protected]>
Update the roadmap's cross-references after the shipped Tier 2 work:
add the newer design docs to the synthesis list, drop the explicit
count of unreferenced requirements, note that the LSP library refactor
unblocks Tier 3, and reflect 2D/2E in the sequencing.

Original prompt:

> Let's remove the already-done parts from it, refresh the stale info and
> drop the explicit mention of number of untracked requirements, and
> finally update the banner, in 3 commits.

AI-assisted-by: DeepSeek V4.1 Flash (OpenCode)
Signed-off-by: WANG Xuerui <[email protected]>
Move the "as of" date forward and list the capabilities shipped since
the previous baseline.

Original prompt:

> Let's remove the already-done parts from it, refresh the stale info and
> drop the explicit mention of number of untracked requirements, and
> finally update the banner, in 3 commits.

AI-assisted-by: DeepSeek V4.1 Flash (OpenCode)
Signed-off-by: WANG Xuerui <[email protected]>
@xen0n
xen0n merged commit 971125b into liyi-run:main Sep 19, 2026
6 checks passed
@xen0n
xen0n deleted the template-sync-and-polishes branch September 19, 2026 08:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant