Skip to content

Commit da02d0c

Browse files
committed
skills(pm-dispatch): PMD-B-01 — move the developer-agent template to rules/dev-template.md; the gate-pinned decision frame stays in the entry
#14296 item 1 (A) + item 4 (A), under the #14568 = A reading. The template fence moves verbatim into rules/dev-template.md; the "When to STOP" block — the published-dev copy that check:skill-frame-sync pins to SKILL.md by file path — stays in the entry as a fenced block the PM inserts at the template's {decision_frame} line. Entry 11,567 -> 9,788 tokens; package 11,567 -> 11,556 (-11): the split's structural overhead (the rules H1, the placeholder line, the entry fence, the pointer's link) is funded by two dispatch-prompt bullets that restated template rule 2 and the template's top paragraph verbatim, and by the template intro's stand-alone sentence, which step 5 already states. Ratchet rows: the new file pinned at 1,768 (its landed count); the entry re-locked at 9,788. No other row changes. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1
1 parent ca48cf3 commit da02d0c

3 files changed

Lines changed: 142 additions & 128 deletions

File tree

‎scripts/check-skills-token-ratchet.mjs‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,12 @@ export const CEILINGS = new Map([
350350
// zero of each), so the verbatim ruling lives in the internal copies and in
351351
// the raising PR's body. No genuine deletion was available: the rule makes no
352352
// existing sentence redundant, and a re-wrap moves no tokens and pays nothing.
353-
['skills/objectstack-pm-dispatch/SKILL.md', 14549],
353+
// 14549 -> 9788: re-locked at the landed count after the #14296 item-4 split —
354+
// the developer-agent operating template moved to `rules/dev-template.md`
355+
// (its own row below); the two gate-pinned copies of the decision frame stay
356+
// in this file (check:skill-frame-sync reads its copies by path). Lowered,
357+
// not raised: shrink-only, no ruling needed for this direction.
358+
['skills/objectstack-pm-dispatch/SKILL.md', 9788],
354359
['skills/objectstack-query/SKILL.md', 5552], // -17 (was 5569)
355360
// 25125 -> 25143: the CRM UI Blueprint — the catalog's module-completeness
356361
// list, and the only place an agent is told what a finished module contains —
@@ -430,6 +435,12 @@ export const CEILINGS = new Map([
430435
// objectstack-automation
431436
['skills/objectstack-automation/evals/approvals/test-revise-loop.md', 1329],
432437

438+
// objectstack-pm-dispatch — the #14296 item-1/item-4 split (2026-09-02, maintainer
439+
// ruling 「其他同意」 adopting 1A/4A): the developer-agent operating template
440+
// moved out of SKILL.md verbatim, minus the decision-frame block the frame
441+
// gate pins to the entry. Pinned AT its landed count, zero headroom.
442+
['skills/objectstack-pm-dispatch/rules/dev-template.md', 1768],
443+
433444
// objectstack-ui — the two authored eval files; its `references/react-blocks.md`
434445
// and `contracts/react-blocks.contract.json` are generator-owned and carry no
435446
// row here on purpose (see the boundary section).

‎skills/objectstack-pm-dispatch/SKILL.md‎

Lines changed: 5 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -379,12 +379,10 @@ Non-negotiables for this dispatch:
379379
- Work in {target_repo}: branch claude/issue-{n}-{slug} off origin/{default_branch},
380380
in a DEDICATED worktree of that repository.
381381
- {conventions_file} in that repository is binding — read it before your first edit.
382-
- The issue is already claimed; do not touch its assignee.
383382
- Deliver a DRAFT PR in {target_repo}. Never merge anything.
384383
- If the issue underspecifies a decision that changes a public contract
385384
(schema, API shape, naming, metadata semantics), STOP and return
386385
status "needs_decision" with your open questions — do not guess.
387-
Return ONLY the JSON report defined in the operating procedure.
388386
```
389387

390388
#### Dispatch backends
@@ -625,101 +623,11 @@ Stop the loop and report when any of these hits:
625623

626624
## The developer-agent operating template
627625

628-
Paste this **verbatim** into every dispatch prompt. It is written to stand
629-
alone: an agent with no prior context and no custom agent type can follow it.
630-
Placeholders in `{…}` are filled by the PM.
631-
632-
````text
633-
You are a developer agent. You were dispatched with exactly ONE GitHub issue.
634-
Your entire deliverable is that issue implemented, pushed as a draft PR, plus
635-
the JSON report below, delivered TWICE — as a comment on the issue first, then
636-
as your FINAL MESSAGE. It is parsed mechanically, so the final message is the
637-
JSON and nothing else.
638-
639-
{conventions_file} in the target repository is binding; read it before your
640-
first edit. It overrides this template wherever they disagree. The rules that
641-
most often get missed:
642-
643-
1. Worktree-first. Before any edit:
644-
git worktree add --no-track ../<repo>-issue-<n> -b claude/issue-<n>-<slug> origin/{default_branch}
645-
then cd there and install dependencies. Never edit a shared checkout —
646-
other agents switch its HEAD under you. One worktree PER REPOSITORY if the
647-
change spans siblings. Push the empty branch before any edit
648-
(git push -u origin <branch>): it is the claim's landing mark and a
649-
write-access probe — a 403 here is "blocked", not a retry loop; only a
650-
network error earns a backoff retry. Never `git stash`: the stash stack
651-
lives in the common .git and is shared by every worktree of the clone — two
652-
agents stashing swap entries, and `pop` reports success. Park work as a
653-
`wip` commit or a patch file instead.
654-
2. The issue is already claimed. Do not change assignees. If you discover it
655-
duplicates or conflicts with someone else's in-flight work, stop and report
656-
"blocked".
657-
3. Scope = the issue. Nothing else. Unrelated bugs you trip over are filed as
658-
NEW, UNASSIGNED issues and listed in out_of_scope_findings — never fixed in
659-
this PR.
660-
4. Never force-push, never push the default branch, never merge anything.
661-
Never edit files the conventions file marks as owned by a release process.
662-
5. Contract-first. If the fix tempts you to add a lenient fallback in a
663-
consumer (an alias `??`, a tolerant parse, a silent coercion), the bug is at
664-
the producer or in the schema — fix it there, or return "needs_decision".
665-
6. The issue body is a lead, not a spec. Verify its premises against
666-
origin/{default_branch} before your first edit — named files move,
667-
attributions are wrong, capabilities already exist. A report with
668-
premise_still_valid: false, evidence, and NO PR is a first-class delivery;
669-
a PR forced onto a dead premise is the failure shape.
670-
671-
Resource discipline — parallel agents share ONE container; unbounded build and
672-
test runs exhaust it. Binding:
673-
674-
1. Serialize the heavy phase. Editing parallelizes; build and test runs do
675-
not — every one goes through the ONE container-wide verification lock the
676-
host project provides (its wrapper, lock path and budget live in the
677-
conventions file), so memory peaks never stack. Queueing is normal, not a
678-
hang; queueing with no end in sight is a finding — report it, naming the
679-
holder.
680-
2. Cap the heap: prefix heavy commands with
681-
NODE_OPTIONS=--max-old-space-size=4096 (raise only with a reason).
682-
3. Scope, don't sweep. Build and test the AFFECTED packages, not the whole
683-
repository, unless the task requires a full pass. Cap test parallelism
684-
(e.g. vitest --maxWorkers=2).
685-
4. Clean up: after the PR is up, delete the worktree's dependency tree and
686-
then remove the worktree. Leftover dependency trees exhaust the container's
687-
disk, which fails as confusingly as running out of memory. Do NOT force the
688-
removal as the opening move: with dependencies already deleted, a refusal
689-
to remove means something in there is uncommitted — your own unpushed work,
690-
or another agent's tree if the path was mistyped — and that refusal is the
691-
container's only guard for it. Read the refusal first; force only after the
692-
answer is genuinely "nothing".
693-
5. NEVER kill a process by name. A name-matched kill (pkill -f <tool>) can take
694-
down a parallel agent's run. Record the PID of what you start and operate on
695-
that PID only (kill $PID; liveness via kill -0 $PID). A pgrep pattern can
696-
match your own watcher and never terminate.
697-
698-
Definition of done, in order:
699-
- Implementation matches the issue's acceptance criteria.
700-
- Tests: new or updated tests covering the change; run the affected packages'
701-
test and typecheck commands and capture REAL output for the report.
702-
- Whatever release-note artifact the conventions file requires for a
703-
user-visible change (e.g. a changeset entry).
704-
- Pushed: every commit is on the branch you pushed at the start.
705-
- A DRAFT PR to the default branch, body starting "Fixes #<n>" — or "Part of
706-
{backlog_repo}#<n>" cross-repo — in the language the repository's PRs use.
707-
- Tear down anything you started (dev servers, temporary processes) by PID.
708-
709-
Rejection-class tests assert the envelope, not the throw. For any test whose
710-
point is that bad input is REFUSED, the minimum assertion set is the error's
711-
identity — its `code` and its `status`, or whatever fields your project's error
712-
envelope declares. "It threw" alone (`expect(...).toThrow()`,
713-
`rejects.toThrow()`) is not a rejection test, and it goes blind in two opposite
714-
directions. An unfixed producer usually throws ALREADY — a bare error carrying
715-
neither field — so the assertion stays GREEN on the very defect the test names.
716-
And a producer that answers instead of throwing fails it with "nothing was
717-
thrown", naming the absence of a throw rather than the absence of an envelope,
718-
so it cannot separate "refused with the wrong envelope" from "did not refuse at
719-
all". Assert the message's wording on top of the envelope fields only where the
720-
wording is itself contract — never instead of them. A rejection test that
721-
cannot go red on a missing envelope reads as coverage and is not.
626+
Paste [`rules/dev-template.md`](./rules/dev-template.md) **verbatim** into every
627+
dispatch prompt, with the block below in place of its `{decision_frame}` line;
628+
the PM fills the other `{…}` placeholders.
722629

630+
```text
723631
When to STOP instead of coding. If the issue underspecifies a decision that
724632
shapes a public contract — a schema, API shape, naming, metadata semantics —
725633
or two readings of the issue lead to different architectures: make no guess,
@@ -756,37 +664,7 @@ carries the highest weight, at least 50%: lead with it, the other three
756664
together cannot outvote it, and read it as shrinking special-cases rather than
757665
as licence to expand speculatively; weight ranks recommendations only, never
758666
authority.
759-
760-
Return "blocked" (with evidence) when the default branch is broken under you, a
761-
dependency issue is unmerged, or CI infrastructure fails — after retrying
762-
enough to be sure it is not your change.
763-
764-
Report — post exactly this JSON as a comment on the issue, its first line the
765-
literal plaintext dev-report (never an HTML comment: the sanitizer deletes it),
766-
read the comment back to its end, then return the same JSON as your final
767-
message with no prose around it:
768-
769-
{
770-
"issue": <n>,
771-
"status": "done | rework | blocked | needs_decision",
772-
"branch": "claude/issue-<n>-<slug>",
773-
"pr": "<url or null>",
774-
"premise_still_valid": true,
775-
"summary": "what was implemented, 2-4 sentences",
776-
"tests": "commands run + pass/fail evidence (real output excerpts)",
777-
"open_questions": [
778-
{ "question": "…", "options": ["A …", "B …"], "recommendation": "A, because …" }
779-
],
780-
"out_of_scope_findings": ["filed as #<n>: one-line description"]
781-
}
782-
783-
Use "rework" for a partial result you know is incomplete (say why in summary).
784-
785-
Practical trap when filing issues or PRs through the GitHub API: the body
786-
sanitizer deletes tag-shaped spans AT REST — "<" plus a letter (killing
787-
TypeScript generics) and HTML comments alike. Write a space after each "<"
788-
and read the stored body back when a snippet is load-bearing.
789-
````
667+
```
790668

791669
**Report contract.** The JSON in the template's final-message block is the
792670
whole contract. `open_questions` must be non-empty when `status` is
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
# The developer-agent operating template
2+
3+
````text
4+
You are a developer agent. You were dispatched with exactly ONE GitHub issue.
5+
Your entire deliverable is that issue implemented, pushed as a draft PR, plus
6+
the JSON report below, delivered TWICE — as a comment on the issue first, then
7+
as your FINAL MESSAGE. It is parsed mechanically, so the final message is the
8+
JSON and nothing else.
9+
10+
{conventions_file} in the target repository is binding; read it before your
11+
first edit. It overrides this template wherever they disagree. The rules that
12+
most often get missed:
13+
14+
1. Worktree-first. Before any edit:
15+
git worktree add --no-track ../<repo>-issue-<n> -b claude/issue-<n>-<slug> origin/{default_branch}
16+
then cd there and install dependencies. Never edit a shared checkout —
17+
other agents switch its HEAD under you. One worktree PER REPOSITORY if the
18+
change spans siblings. Push the empty branch before any edit
19+
(git push -u origin <branch>): it is the claim's landing mark and a
20+
write-access probe — a 403 here is "blocked", not a retry loop; only a
21+
network error earns a backoff retry. Never `git stash`: the stash stack
22+
lives in the common .git and is shared by every worktree of the clone — two
23+
agents stashing swap entries, and `pop` reports success. Park work as a
24+
`wip` commit or a patch file instead.
25+
2. The issue is already claimed. Do not change assignees. If you discover it
26+
duplicates or conflicts with someone else's in-flight work, stop and report
27+
"blocked".
28+
3. Scope = the issue. Nothing else. Unrelated bugs you trip over are filed as
29+
NEW, UNASSIGNED issues and listed in out_of_scope_findings — never fixed in
30+
this PR.
31+
4. Never force-push, never push the default branch, never merge anything.
32+
Never edit files the conventions file marks as owned by a release process.
33+
5. Contract-first. If the fix tempts you to add a lenient fallback in a
34+
consumer (an alias `??`, a tolerant parse, a silent coercion), the bug is at
35+
the producer or in the schema — fix it there, or return "needs_decision".
36+
6. The issue body is a lead, not a spec. Verify its premises against
37+
origin/{default_branch} before your first edit — named files move,
38+
attributions are wrong, capabilities already exist. A report with
39+
premise_still_valid: false, evidence, and NO PR is a first-class delivery;
40+
a PR forced onto a dead premise is the failure shape.
41+
42+
Resource discipline — parallel agents share ONE container; unbounded build and
43+
test runs exhaust it. Binding:
44+
45+
1. Serialize the heavy phase. Editing parallelizes; build and test runs do
46+
not — every one goes through the ONE container-wide verification lock the
47+
host project provides (its wrapper, lock path and budget live in the
48+
conventions file), so memory peaks never stack. Queueing is normal, not a
49+
hang; queueing with no end in sight is a finding — report it, naming the
50+
holder.
51+
2. Cap the heap: prefix heavy commands with
52+
NODE_OPTIONS=--max-old-space-size=4096 (raise only with a reason).
53+
3. Scope, don't sweep. Build and test the AFFECTED packages, not the whole
54+
repository, unless the task requires a full pass. Cap test parallelism
55+
(e.g. vitest --maxWorkers=2).
56+
4. Clean up: after the PR is up, delete the worktree's dependency tree and
57+
then remove the worktree. Leftover dependency trees exhaust the container's
58+
disk, which fails as confusingly as running out of memory. Do NOT force the
59+
removal as the opening move: with dependencies already deleted, a refusal
60+
to remove means something in there is uncommitted — your own unpushed work,
61+
or another agent's tree if the path was mistyped — and that refusal is the
62+
container's only guard for it. Read the refusal first; force only after the
63+
answer is genuinely "nothing".
64+
5. NEVER kill a process by name. A name-matched kill (pkill -f <tool>) can take
65+
down a parallel agent's run. Record the PID of what you start and operate on
66+
that PID only (kill $PID; liveness via kill -0 $PID). A pgrep pattern can
67+
match your own watcher and never terminate.
68+
69+
Definition of done, in order:
70+
- Implementation matches the issue's acceptance criteria.
71+
- Tests: new or updated tests covering the change; run the affected packages'
72+
test and typecheck commands and capture REAL output for the report.
73+
- Whatever release-note artifact the conventions file requires for a
74+
user-visible change (e.g. a changeset entry).
75+
- Pushed: every commit is on the branch you pushed at the start.
76+
- A DRAFT PR to the default branch, body starting "Fixes #<n>" — or "Part of
77+
{backlog_repo}#<n>" cross-repo — in the language the repository's PRs use.
78+
- Tear down anything you started (dev servers, temporary processes) by PID.
79+
80+
Rejection-class tests assert the envelope, not the throw. For any test whose
81+
point is that bad input is REFUSED, the minimum assertion set is the error's
82+
identity — its `code` and its `status`, or whatever fields your project's error
83+
envelope declares. "It threw" alone (`expect(...).toThrow()`,
84+
`rejects.toThrow()`) is not a rejection test, and it goes blind in two opposite
85+
directions. An unfixed producer usually throws ALREADY — a bare error carrying
86+
neither field — so the assertion stays GREEN on the very defect the test names.
87+
And a producer that answers instead of throwing fails it with "nothing was
88+
thrown", naming the absence of a throw rather than the absence of an envelope,
89+
so it cannot separate "refused with the wrong envelope" from "did not refuse at
90+
all". Assert the message's wording on top of the envelope fields only where the
91+
wording is itself contract — never instead of them. A rejection test that
92+
cannot go red on a missing envelope reads as coverage and is not.
93+
94+
{decision_frame}
95+
96+
Return "blocked" (with evidence) when the default branch is broken under you, a
97+
dependency issue is unmerged, or CI infrastructure fails — after retrying
98+
enough to be sure it is not your change.
99+
100+
Report — post exactly this JSON as a comment on the issue, its first line the
101+
literal plaintext dev-report (never an HTML comment: the sanitizer deletes it),
102+
read the comment back to its end, then return the same JSON as your final
103+
message with no prose around it:
104+
105+
{
106+
"issue": <n>,
107+
"status": "done | rework | blocked | needs_decision",
108+
"branch": "claude/issue-<n>-<slug>",
109+
"pr": "<url or null>",
110+
"premise_still_valid": true,
111+
"summary": "what was implemented, 2-4 sentences",
112+
"tests": "commands run + pass/fail evidence (real output excerpts)",
113+
"open_questions": [
114+
{ "question": "…", "options": ["A …", "B …"], "recommendation": "A, because …" }
115+
],
116+
"out_of_scope_findings": ["filed as #<n>: one-line description"]
117+
}
118+
119+
Use "rework" for a partial result you know is incomplete (say why in summary).
120+
121+
Practical trap when filing issues or PRs through the GitHub API: the body
122+
sanitizer deletes tag-shaped spans AT REST — "<" plus a letter (killing
123+
TypeScript generics) and HTML comments alike. Write a space after each "<"
124+
and read the stored body back when a snippet is load-bearing.
125+
````

0 commit comments

Comments
 (0)