Skip to content

Commit 0aa1d05

Browse files
committed
chore(harness): add Plan-by footer, source Acked-by/Plan-by from opencode.json
Every conventional commit now includes three footers: Plan-by (sourced from agent.plan.model in opencode.json), Acked-by (sourced from agent.build.model), and Signed-off-by. Both model IDs use the segment after the last "/" — e.g. openrouter/z-ai/glm-5.2 becomes glm-5.2. Acked-by is no longer the runtime model; it now reads the configured build model from opencode.json, making both footers stable references. commitlint enforces all three via a custom trailers-exist plugin rule (replaces the built-in trailer-exists which only accepts a single string). Updated the conventional-commits skill, tdd agent, writing-plans skill, finishing-a-development-branch skill, AGENTS.md, README.md, and commitlint.config.js. Plan-by: glm-5.2 Acked-by: deepseek-v4-pro Signed-off-by: kyau <git@kyaulabs.com>
1 parent 628dba5 commit 0aa1d05

7 files changed

Lines changed: 67 additions & 22 deletions

File tree

.opencode/agents/tdd.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,8 @@ and produce a commit message in the required format:
142142
- Type and optional scope from the work performed (feat, fix, test, docs,
143143
chore, etc.)
144144
- Subject: lowercase, no period, ≤ 100 chars, describes what changed
145-
- Footer: `Acked-by:` with the current model ID in kebab-case
145+
- Footer: `Plan-by:` with `agent.plan.model` from `opencode.json`, segment after the last `/` (e.g. `openrouter/z-ai/glm-5.2``glm-5.2`)
146+
- Footer: `Acked-by:` with `agent.build.model` from `opencode.json`, segment after the last `/` (e.g. `deepseek/deepseek-v4-pro``deepseek-v4-pro`)
146147
- Footer: `Signed-off-by: kyau <git@kyaulabs.com>`
147148

148149
If the task already provided a commit message in the plan, validate it —

.opencode/skills/conventional-commits/SKILL.md

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,26 +16,30 @@ description: Use when writing or reviewing commit messages. Covers the required
1616
- Subject line: lowercase, no period at end, max 100 characters
1717
- Body: wrap at 72 characters, explain *why* not *what*
1818
- Signed commits required (`git commit -S`)
19-
- Every commit must include `Acked-by:` (agent in kebab-case) and `Signed-off-by:` (user) footers
19+
- Every commit must include `Plan-by:`, `Acked-by:`, and `Signed-off-by:` footers
2020

2121
## Required Footers
2222

23-
Every commit message must end with two footers:
23+
Every commit message must end with three footers:
2424

25-
- **`Acked-by:`** — the AI agent that authored the change, in kebab-case.
26-
Use the model ID without slashes. Examples: `deepseek-v4-pro`,
27-
`claude-sonnet-4`, `gemini-2-5-pro`.
25+
- **`Plan-by:`** — the planning model, in kebab-case. Sourced from
26+
`agent.plan.model` in `opencode.json` (the segment after the last `/`).
27+
Example: `openrouter/z-ai/glm-5.2``glm-5.2`.
28+
- **`Acked-by:`** — the build model, in kebab-case. Sourced from
29+
`agent.build.model` in `opencode.json` (the segment after the last `/`).
30+
Example: `deepseek/deepseek-v4-pro``deepseek-v4-pro`.
2831

2932
> [!CAUTION]
30-
> Do NOT use role names (`build-agent`, `code-review`, `tdd`, etc.) —
31-
> only the model ID. The Acked-by tracks which model produced the
32-
> commit, not which agent role orchestrated it.
33+
> Do NOT use role names (`build-agent`, `code-review`, `tdd`, etc.) — only the
34+
> model ID. The Plan-by and Acked-by footers track which configured models
35+
> planned and built the change, not which agent role orchestrated it.
3336
- **`Signed-off-by:`** — the human user approving the change, formatted as
3437
`username <email>`. Default when no user is specified:
3538
`kyau <git@kyaulabs.com>`.
3639

37-
These are mandatory for traceability. The agent writes them automatically based
38-
on the current model and the AGENTS.md default.
40+
These are mandatory for traceability. The agent writes them automatically by
41+
reading `agent.plan.model` and `agent.build.model` from `opencode.json` and
42+
taking the segment after the last `/`.
3943

4044
## Valid Types
4145

@@ -79,13 +83,15 @@ directory, or feature area: `feat(aurora)`, `fix(db)`, `test(auth)`.
7983
```
8084
feat(auth): add remember-me cookie to login flow
8185
86+
Plan-by: glm-5.2
8287
Acked-by: deepseek-v4-pro
8388
Signed-off-by: kyau <git@kyaulabs.com>
8489
```
8590

8691
```
8792
fix(db): prevent SQL injection in user search query
8893
94+
Plan-by: glm-5.2
8995
Acked-by: deepseek-v4-pro
9096
Signed-off-by: kyau <git@kyaulabs.com>
9197
Fixes: #42
@@ -94,6 +100,7 @@ Fixes: #42
94100
```
95101
test(auth): add boundary cases for empty credentials
96102
103+
Plan-by: glm-5.2
97104
Acked-by: deepseek-v4-pro
98105
Signed-off-by: kyau <git@kyaulabs.com>
99106
```

.opencode/skills/finishing-a-development-branch/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ Run every item; do not skip:
2929
- [ ] No merge conflicts after rebase. If conflicts exist, suggest
3030
`@resolve-merge-conflicts` or manual resolution.
3131
- [ ] All commits follow Conventional Commits format and include
32-
`Acked-by:` + `Signed-off-by:` footers (see `conventional-commits` skill).
32+
`Plan-by:` + `Acked-by:` + `Signed-off-by:` footers (see `conventional-commits` skill).
3333

3434
After every item passes, present the summary below. If any item fails, stop
3535
and fix it — do not proceed with a failing item.

.opencode/skills/writing-plans/SKILL.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ Expected: PASS
146146
git add tests/path/to/Test.php backend/path/to/file.php
147147
git commit -S -m "feat(scope): concise subject describing the change
148148
149+
Plan-by: glm-5.2
149150
Acked-by: deepseek-v4-pro
150151
Signed-off-by: kyau <git@kyaulabs.com>"
151152
```
@@ -165,7 +166,7 @@ failures** — never write them:
165166
code steps)
166167
- References to types, functions, or methods not defined in any task
167168
- Bare commit messages missing scope or required footers — use the full
168-
conventional-commits format (type[scope]: subject + Acked-by + Signed-off-by)
169+
conventional-commits format (type[scope]: subject + Plan-by + Acked-by + Signed-off-by)
169170

170171
## Self-review
171172

AGENTS.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -134,14 +134,15 @@ For linting details and responsive/mobile-first CSS rules, see `scss-mobile-firs
134134
- Features: `feat/<username>-<hash>-<description>`
135135
- Commits: Conventional Commits format (type[scope]: subject) — see `conventional-commits` skill
136136
- Signed commits required
137-
- Every commit must include `Acked-by:` (model ID in kebab-case, e.g. `deepseek-v4-pro`) and `Signed-off-by:` (user) footers. Default Signed-off-by: `kyau <git@kyaulabs.com>`.
137+
- Every commit must include `Plan-by:` (sourced from `agent.plan.model` in `opencode.json`), `Acked-by:` (sourced from `agent.build.model` in `opencode.json` — model ID segment after the last `/`), and `Signed-off-by:` (user) footers. Default Signed-off-by: `kyau <git@kyaulabs.com>`.
138138
- No squash merges. Each logical change is its own atomic commit — the git history serves as the development and evaluation log. A pre-push hook warns on single-commit branches that look like squashes.
139139

140140
After implementing any change — whether via @tdd, a direct fix, an issue
141141
tracker resolution, or a fast-path trivial change — produce a commit message
142142
in conventional commits format before committing. Load the
143-
`conventional-commits` skill and produce: type[scope]: subject + Acked-by +
144-
Signed-off-by footers. The commit-msg hook blocks invalid messages, but the
143+
`conventional-commits` skill and produce: type[scope]: subject + Plan-by +
144+
Acked-by + Signed-off-by footers. The commit-msg hook blocks invalid messages,
145+
but the
145146
message should be well-formed before you reach the hook.
146147

147148
### Commit and push permissions

README.md

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -580,7 +580,8 @@ Longer commit body with additional contextual information about the code changes
580580
<token>: <value>
581581
(max-length: 100)
582582
token (Sentence-case) = {
583-
'Acked-by', # Required — the AI agent in kebab-case (e.g., deepseek-v4-pro)
583+
'Plan-by', # Required — the planning model from opencode.json (e.g., glm-5.2)
584+
'Acked-by', # Required — the build model from opencode.json (e.g., deepseek-v4-pro)
584585
'Signed-off-by', # Required — the user (e.g., kyau <git@kyaulabs.com>)
585586
'BREAKING CHANGE', # Required when the type/scope includes !
586587
'Cc',
@@ -591,10 +592,11 @@ token (Sentence-case) = {
591592
}
592593
```
593594
594-
Every commit must include `Acked-by` and `Signed-off-by` footers. If no user is
595-
explicitly named, the default `Signed-off-by` is `kyau <git@kyaulabs.com>`. The
596-
`Acked-by` value is the agent that authored the change in kebab-case (use the
597-
model ID without slashes, e.g., `deepseek-v4-pro`, `claude-sonnet-4`).
595+
Every commit must include `Plan-by`, `Acked-by`, and `Signed-off-by` footers. If
596+
no user is explicitly named, the default `Signed-off-by` is `kyau
597+
<git@kyaulabs.com>`. `Plan-by` and `Acked-by` are sourced from `agent.plan.model`
598+
and `agent.build.model` in `opencode.json` (the segment after the last `/`),
599+
e.g. `deepseek/deepseek-v4-pro``deepseek-v4-pro`.
598600
599601
### Examples
600602
@@ -609,6 +611,7 @@ As per #123 recommendation input controller is now based on blah.
609611

610612
Basic movement added.
611613

614+
Plan-by: glm-5.2
612615
Acked-by: deepseek-v4-pro
613616
Signed-off-by: kyau <git@kyaulabs.com>
614617
Refs: #123
@@ -620,6 +623,7 @@ fix: array parsing issue
620623

621624
Fixes: #42
622625
Cc: Z
626+
Plan-by: glm-5.2
623627
Acked-by: deepseek-v4-pro
624628
Reviewed-by: Z
625629
Signed-off-by: kyau <git@kyaulabs.com>

commitlint.config.js

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,34 @@
1+
// $KYAULabs: commitlint.config.js kyau@nova 2026/07/05 -0700 Exp $
2+
3+
const { spawnSync } = require('child_process');
4+
5+
const trailersExist = (parsed, when, trailers) => {
6+
const output = spawnSync('git', ['interpret-trailers', '--parse'], {
7+
input: parsed.raw || '',
8+
}).stdout.toString();
9+
const lines = output.split('\n');
10+
const negated = when === 'never';
11+
const missing = trailers.filter(
12+
(t) => !lines.some((ln) => ln.startsWith(t))
13+
);
14+
const allPresent = missing.length === 0;
15+
return [
16+
negated ? !allPresent : allPresent,
17+
'message must have ' +
18+
trailers.map((t) => '`' + t + '`').join(', ') +
19+
' trailer' + (trailers.length > 1 ? 's' : ''),
20+
];
21+
};
22+
123
module.exports = {
224
extends: ['@commitlint/config-conventional'],
25+
plugins: [
26+
{
27+
rules: {
28+
'trailers-exist': trailersExist,
29+
},
30+
},
31+
],
332
rules: {
433
'header-max-length': [2, 'always', 100],
534
'type-enum': [2, 'always', [
@@ -17,7 +46,9 @@ module.exports = {
1746
'test',
1847
'ignore',
1948
]],
20-
'trailer-exists': [2, 'always', 'Acked-by:'],
49+
'trailers-exist': [2, 'always', ['Plan-by:', 'Acked-by:']],
2150
'signed-off-by': [2, 'always', 'Signed-off-by:'],
2251
},
2352
};
53+
54+
// vim: ft=javascript sts=4 sw=4 ts=4 noet :

0 commit comments

Comments
 (0)