Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
326 commits
Select commit Hold shift + click to select a range
c4bf437
ticket(0ca3e019): set effort = 4
philippepascal Apr 30, 2026
43d5ade
ticket(0ca3e019): set risk = 3
philippepascal Apr 30, 2026
a626c3b
ticket(0ca3e019): set section Approach
philippepascal Apr 30, 2026
0f3e1e9
ticket(0ca3e019): in_design → specd
philippepascal Apr 30, 2026
34d20be
ticket(25c92daa): start — groomed → in_design
philippepascal Apr 30, 2026
37b6a53
ticket(71d80e40): start — groomed → in_design
philippepascal Apr 30, 2026
939a62a
ticket(2e772eab): start — groomed → in_design
philippepascal Apr 30, 2026
c249c61
ticket(25c92daa): in_design → ammend
philippepascal May 1, 2026
b812586
ticket(71d80e40): in_design → ammend
philippepascal May 1, 2026
234a3ae
ticket(2c32a282): specd → ammend
philippepascal May 1, 2026
5f5db02
ticket(2e772eab): in_design → ammend
philippepascal May 1, 2026
5c7aa37
ticket(25c92daa): start — ammend → in_design
philippepascal May 1, 2026
4902440
ticket(25c92daa): set section Acceptance criteria
philippepascal May 1, 2026
6e32e78
ticket(25c92daa): set section Out of scope
philippepascal May 1, 2026
c8863d4
ticket(25c92daa): set section Approach
philippepascal May 1, 2026
55e9241
ticket(25c92daa): set effort = 6
philippepascal May 1, 2026
adbe817
ticket(25c92daa): set risk = 4
philippepascal May 1, 2026
162852a
ticket(25c92daa): set section Approach
philippepascal May 1, 2026
726861f
ticket(25c92daa): in_design → specd
philippepascal May 1, 2026
94443a8
ticket(71d80e40): start — ammend → in_design
philippepascal May 1, 2026
8a1b65d
ticket(71d80e40): set section Acceptance criteria
philippepascal May 1, 2026
b54be01
ticket(71d80e40): set section Out of scope
philippepascal May 1, 2026
3f5143b
ticket(71d80e40): set section Approach
philippepascal May 1, 2026
52520d2
ticket(71d80e40): set effort = 5
philippepascal May 1, 2026
58b535e
ticket(71d80e40): set risk = 3
philippepascal May 1, 2026
7399958
ticket(71d80e40): in_design → specd
philippepascal May 1, 2026
79da319
ticket(2e772eab): start — ammend → in_design
philippepascal May 1, 2026
1153b69
ticket(2e772eab): set section Acceptance criteria
philippepascal May 1, 2026
b3c8c49
ticket(2e772eab): set section Out of scope
philippepascal May 1, 2026
64ab868
ticket(2e772eab): set section Approach
philippepascal May 1, 2026
4fdf0c5
ticket(2e772eab): set effort = 2
philippepascal May 1, 2026
dbebb09
ticket(2e772eab): set risk = 1
philippepascal May 1, 2026
58e9684
ticket(2e772eab): in_design → specd
philippepascal May 1, 2026
6717b80
ticket(2803bf07): start — groomed → in_design
philippepascal May 1, 2026
80e5508
ticket(2803bf07): set section Acceptance criteria
philippepascal May 1, 2026
ca9d288
ticket(2803bf07): set section Out of scope
philippepascal May 1, 2026
2341e1d
ticket(2803bf07): set section Approach
philippepascal May 1, 2026
b126603
ticket(2803bf07): set effort = 5
philippepascal May 1, 2026
0252a94
ticket(2803bf07): set risk = 3
philippepascal May 1, 2026
7cdc3fa
ticket(2803bf07): fix Approach section body (remove leading subheading)
philippepascal May 1, 2026
12bbf1b
ticket(2803bf07): in_design → specd
philippepascal May 1, 2026
1c8a674
ticket(2c32a282): start — ammend → in_design
philippepascal May 1, 2026
8309a53
ticket(2c32a282): set section Problem
philippepascal May 1, 2026
9b0e4ad
ticket(2c32a282): set effort = 5
philippepascal May 1, 2026
51198be
ticket(2c32a282): set risk = 3
philippepascal May 1, 2026
752ccb2
ticket(2c32a282): in_design → specd
philippepascal May 1, 2026
9dc42c7
ticket(d3b93b95): set section Amendment requests
philippepascal May 1, 2026
90d8beb
ticket(a1b94ea4): set section Amendment requests
philippepascal May 1, 2026
5bd707d
ticket(6cac8518): set section Amendment requests
philippepascal May 1, 2026
f66f818
ticket(2c32a282): set section Amendment requests
philippepascal May 1, 2026
61157de
ticket(0ca3e019): set section Amendment requests
philippepascal May 1, 2026
c159988
ticket(25c92daa): set section Amendment requests
philippepascal May 1, 2026
60c14a4
ticket(71d80e40): set section Amendment requests
philippepascal May 1, 2026
58a26d0
ticket(2803bf07): set section Amendment requests
philippepascal May 1, 2026
8713176
ticket(3048d7e9): set section Amendment requests
philippepascal May 1, 2026
1983d3c
ticket(d3b93b95): specd → ammend
philippepascal May 1, 2026
07f569a
ticket(a1b94ea4): specd → ammend
philippepascal May 1, 2026
9b3ffa3
ticket(6cac8518): specd → ammend
philippepascal May 1, 2026
7e4b2ab
ticket(2c32a282): specd → ammend
philippepascal May 1, 2026
112a0d3
ticket(0ca3e019): specd → ammend
philippepascal May 1, 2026
d146b19
ticket(25c92daa): specd → ammend
philippepascal May 1, 2026
d4b9a9b
ticket(71d80e40): specd → ammend
philippepascal May 1, 2026
cd2a467
ticket(2803bf07): specd → ammend
philippepascal May 1, 2026
86b8de2
ticket(3048d7e9): specd → ammend
philippepascal May 1, 2026
88cb1dc
ticket(d3b93b95): start — ammend → in_design
philippepascal May 1, 2026
e8fc416
ticket(d3b93b95): set section Acceptance criteria
philippepascal May 1, 2026
8969e51
ticket(d3b93b95): set section Approach
philippepascal May 1, 2026
f6a9848
ticket(d3b93b95): mark "Add `APM_BIN=<absolute path>` to the wrapper-…
philippepascal May 1, 2026
4e00c16
ticket(d3b93b95): mark "Tighten the `APM_ROLE_PREFIX` AC to match the…
philippepascal May 1, 2026
528c7e6
ticket(d3b93b95): in_design → specd
philippepascal May 1, 2026
25f9541
ticket(a1b94ea4): start — ammend → in_design
philippepascal May 1, 2026
a8f656e
ticket(a1b94ea4): set section Approach
philippepascal May 1, 2026
e191d81
ticket(a1b94ea4): mark "The Approach's transition→outcome mapping tab…
philippepascal May 1, 2026
118e46b
ticket(a1b94ea4): in_design → specd
philippepascal May 1, 2026
77d6eb8
ticket(3048d7e9): start — ammend → in_design
philippepascal May 1, 2026
d87e770
ticket(3048d7e9): set section Approach
philippepascal May 1, 2026
a6db619
ticket(3048d7e9): mark "Add a one-line note to the Approach explicitl…
philippepascal May 1, 2026
0d95b95
ticket(3048d7e9): in_design → specd
philippepascal May 1, 2026
f31c992
ticket(0ca3e019): start — ammend → in_design
philippepascal May 1, 2026
a12b24c
ticket(0ca3e019): set section Acceptance criteria
philippepascal May 1, 2026
c473f91
ticket(0ca3e019): set section Approach
philippepascal May 1, 2026
35a70bd
ticket(0ca3e019): mark "Add a cross-ticket TODO note in the Approach:…
philippepascal May 1, 2026
a891b98
ticket(0ca3e019): in_design → specd
philippepascal May 1, 2026
b2f9af8
ticket(6cac8518): start — ammend → in_design
philippepascal May 1, 2026
8ebba7b
ticket(6cac8518): set section Acceptance criteria
philippepascal May 1, 2026
3db525c
ticket(6cac8518): set section Approach
philippepascal May 1, 2026
72893df
ticket(6cac8518): mark "The deprecation-warning test AC currently ass…
philippepascal May 1, 2026
2d12a59
ticket(6cac8518): in_design → specd
philippepascal May 1, 2026
ccb8e3a
ticket(2c32a282): start — ammend → in_design
philippepascal May 1, 2026
d20d5bb
ticket(2c32a282): set section Approach
philippepascal May 1, 2026
0736041
ticket(2c32a282): mark "Make the two-layer manifest validation explic…
philippepascal May 1, 2026
1215ffd
ticket(2c32a282): in_design → specd
philippepascal May 1, 2026
2709b08
ticket(71d80e40): start — ammend → in_design
philippepascal May 1, 2026
2932c21
ticket(71d80e40): set section Approach
philippepascal May 1, 2026
1a6c17e
ticket(71d80e40): address amendments
philippepascal May 1, 2026
8e816b9
ticket(71d80e40): in_design → specd
philippepascal May 1, 2026
68e168c
ticket(2803bf07): start — ammend → in_design
philippepascal May 1, 2026
419a8aa
ticket(25c92daa): start — ammend → in_design
philippepascal May 1, 2026
47c6f7e
ticket(25c92daa): set section Acceptance criteria
philippepascal May 1, 2026
3423346
ticket(25c92daa): set section Out of scope
philippepascal May 1, 2026
1486b28
ticket(25c92daa): set section Problem
philippepascal May 1, 2026
8e01227
ticket(25c92daa): mark "Adopt `APM_BIN` as the canonical way for mock…
philippepascal May 1, 2026
58de186
ticket(25c92daa): mark "Reconcile `APM_OPT_SEED`" in Amendment requests
philippepascal May 1, 2026
1f0a606
ticket(25c92daa): mark "Add an AC for mock-random" in Amendment requests
philippepascal May 1, 2026
0220bef
ticket(25c92daa): mark "Mocks need their own per-agent instruction fi…
philippepascal May 1, 2026
0016824
ticket(25c92daa): address amendments
philippepascal May 1, 2026
ef7f746
ticket(25c92daa): in_design → specd
philippepascal May 1, 2026
d914d4f
ui: edit ticket body
philippepascal May 1, 2026
164de8b
ticket(2803bf07): in_design → specd
philippepascal May 1, 2026
846e987
ui: edit ticket body
philippepascal May 1, 2026
9f308e7
ticket(2803bf07): specd → ammend
philippepascal May 1, 2026
8d05474
ticket(2803bf07): start — ammend → in_design
philippepascal May 1, 2026
a37faf1
ticket(2803bf07): set section Problem
philippepascal May 1, 2026
991f38e
ticket(2803bf07): set section Acceptance criteria
philippepascal May 1, 2026
9c3ff22
ticket(2803bf07): set section Out of scope
philippepascal May 1, 2026
9b733a8
ticket(2803bf07): set section Approach
philippepascal May 1, 2026
6ac2ddb
ticket(2803bf07): mark "Drop the `raw` parser mode from this ticket e…
philippepascal May 1, 2026
0029ba0
ticket(2803bf07): mark "Clarify child exit-code semantics for `parser…
philippepascal May 1, 2026
14c9122
ticket(2803bf07): mark "Add an AC for stream-capture loss-prevention:…
philippepascal May 1, 2026
956d052
ticket(2803bf07): in_design → specd
philippepascal May 1, 2026
c87af15
ticket(2803bf07): set section Amendment requests
philippepascal May 1, 2026
c2f18f8
ticket(2803bf07): specd → ammend
philippepascal May 1, 2026
2d3e87f
ticket(2803bf07): start — ammend → in_design
philippepascal May 1, 2026
397fa89
ticket(2803bf07): set section Approach
philippepascal May 1, 2026
4435571
ticket(2803bf07): mark "Drop the `raw` parser mode from this ticket e…
philippepascal May 1, 2026
cb3d5a5
ticket(2803bf07): mark "Clarify child exit-code semantics for `parser…
philippepascal May 1, 2026
6bb087e
ticket(2803bf07): mark "Add an AC for stream-capture loss-prevention:…
philippepascal May 1, 2026
be15d1e
ticket(2803bf07): address amendments — drop raw mode, confirm exit-co…
philippepascal May 1, 2026
c6dcc82
ticket(2803bf07): in_design → specd
philippepascal May 1, 2026
0f2039e
ticket(d3b93b95): specd → ready
philippepascal May 1, 2026
216829c
ticket(a1b94ea4): specd → ready
philippepascal May 1, 2026
3a5ba81
ticket(6cac8518): specd → ready
philippepascal May 1, 2026
6363076
ticket(2c32a282): specd → ready
philippepascal May 1, 2026
8fa2d15
ticket(3048d7e9): specd → ready
philippepascal May 1, 2026
4ffe062
ticket(7f5f73d5): specd → ready
philippepascal May 1, 2026
38e9c25
ticket(0ca3e019): specd → ready
philippepascal May 1, 2026
f060a08
ticket(25c92daa): specd → ready
philippepascal May 1, 2026
ea1ca6e
ticket(71d80e40): specd → ready
philippepascal May 1, 2026
31e3ebe
ticket(2e772eab): specd → ready
philippepascal May 1, 2026
dd06b83
ticket(2803bf07): specd → ready
philippepascal May 1, 2026
a47d2ad
ticket(d3b93b95): start — ready → in_progress
philippepascal May 1, 2026
a7f2a40
Add wrapper contract foundation: Wrapper trait, WrapperContext, Claud…
philippepascal May 1, 2026
0b53ff8
ticket(d3b93b95): mark all acceptance criteria complete
philippepascal May 1, 2026
c3c0ac2
ticket(d3b93b95): in_progress → implemented
philippepascal May 1, 2026
6efa67e
Merge branch 'ticket/d3b93b95-wrapper-contract-foundation-trait-dispa…
philippepascal May 1, 2026
9b98ac1
ticket(a1b94ea4): start — ready → in_progress
philippepascal May 1, 2026
f95cabb
Merge remote-tracking branch 'origin/epic/4312fbd4-agent-wrapper-arch…
philippepascal May 1, 2026
a6c5d6a
Add outcome field to TransitionConfig with resolve_outcome helper
philippepascal May 1, 2026
dc56d67
ticket(a1b94ea4): mark all acceptance criteria complete
philippepascal May 1, 2026
24b8fbf
ticket(a1b94ea4): in_progress → implemented
philippepascal May 1, 2026
5713546
Merge branch 'ticket/a1b94ea4-add-outcome-field-to-transitionconfig-w…
philippepascal May 1, 2026
2c8107f
ticket(6cac8518): start — ready → in_progress
philippepascal May 1, 2026
87c32ab
Merge remote-tracking branch 'origin/epic/4312fbd4-agent-wrapper-arch…
philippepascal May 1, 2026
3dde15c
Add agent/options config fields, wire dispatcher, emit deprecation wa…
philippepascal May 1, 2026
be9b7ec
ticket(6cac8518): mark "WorkersConfig" in Acceptance criteria
philippepascal May 1, 2026
59c7d6c
ticket(6cac8518): mark "WorkerProfileConfig" in Acceptance criteria
philippepascal May 1, 2026
76819d7
ticket(6cac8518): mark "profile.agent` absent" in Acceptance criteria
philippepascal May 1, 2026
2b79b27
ticket(6cac8518): mark "mock-happy" in Acceptance criteria
philippepascal May 1, 2026
21ef734
ticket(6cac8518): mark "neither" in Acceptance criteria
philippepascal May 1, 2026
dfe36f7
ticket(6cac8518): mark "override `workers.options`" in Acceptance cri…
philippepascal May 1, 2026
d87db99
ticket(6cac8518): mark "do not overlap" in Acceptance criteria
philippepascal May 1, 2026
5619cb6
ticket(6cac8518): mark "APM_OPT_<KEY>" in Acceptance criteria
philippepascal May 1, 2026
735e6ad
ticket(6cac8518): mark "APM_OPT_MODEL=sonnet" in Acceptance criteria
philippepascal May 1, 2026
2c53aad
ticket(6cac8518): mark "only legacy" in Acceptance criteria
philippepascal May 1, 2026
23d7ad6
ticket(6cac8518): mark "deprecated` is written" in Acceptance criteria
philippepascal May 1, 2026
98822bf
ticket(6cac8518): mark "Across the lifetime" in Acceptance criteria
philippepascal May 1, 2026
2dd2840
ticket(6cac8518): mark "Legacy `model" in Acceptance criteria
philippepascal May 1, 2026
88d63c1
ticket(6cac8518): mark "apm init" in Acceptance criteria
philippepascal May 1, 2026
b8218c1
ticket(6cac8518): mark "no `[workers]`" in Acceptance criteria
philippepascal May 1, 2026
7f0b13d
ticket(6cac8518): in_progress → implemented
philippepascal May 1, 2026
7ead2c0
Merge branch 'ticket/6cac8518-config-schema-agent-options-drop-comman…
philippepascal May 1, 2026
f45d421
ticket(3048d7e9): start — ready → in_progress
philippepascal May 1, 2026
8f8dac2
Merge remote-tracking branch 'origin/epic/4312fbd4-agent-wrapper-arch…
philippepascal May 1, 2026
554f9ef
Add apm validate --fix migration for legacy command/args/model fields
philippepascal May 1, 2026
4e03c5d
Mark acceptance criteria complete
philippepascal May 1, 2026
9d4b23a
ticket(3048d7e9): in_progress → implemented
philippepascal May 1, 2026
513a2f9
Merge branch 'ticket/3048d7e9-migration-validate-fix-ports-legacy-com…
philippepascal May 1, 2026
d429ee7
ticket(0ca3e019): start — ready → in_progress
philippepascal May 1, 2026
9e2b5e8
Merge remote-tracking branch 'origin/epic/4312fbd4-agent-wrapper-arch…
philippepascal May 1, 2026
38ead85
Add frontmatter agent + agent_overrides for per-ticket wrapper choice
philippepascal May 1, 2026
2b84bb9
ticket(0ca3e019): set section Acceptance criteria
philippepascal May 1, 2026
f4bcc7e
ticket(0ca3e019): in_progress → implemented
philippepascal May 1, 2026
f75b690
Merge branch 'ticket/0ca3e019-frontmatter-agent-agent-overrides-for-p…
philippepascal May 1, 2026
f043f37
ticket(2c32a282): start — ready → in_progress
philippepascal May 1, 2026
846101b
Merge remote-tracking branch 'origin/epic/4312fbd4-agent-wrapper-arch…
philippepascal May 1, 2026
052d026
Add custom wrapper resolution from .apm/agents/<name>/
philippepascal May 1, 2026
7862594
ticket(2c32a282): check off acceptance criteria
philippepascal May 1, 2026
af937de
ticket(2c32a282): in_progress → implemented
philippepascal May 1, 2026
f0c62ce
Merge branch 'ticket/2c32a282-custom-wrapper-resolution-from-apm-agen…
philippepascal May 1, 2026
31e9052
ticket(2e772eab): start — ready → in_progress
philippepascal May 1, 2026
ab0609e
Merge remote-tracking branch 'origin/epic/4312fbd4-agent-wrapper-arch…
philippepascal May 1, 2026
717dee6
Add CONTRACT_VERSION constant and check_contract_version helper
philippepascal May 1, 2026
d69bd7d
Check off all acceptance criteria
philippepascal May 1, 2026
70e51a9
ticket(2e772eab): in_progress → implemented
philippepascal May 1, 2026
98d0cfe
Merge branch 'ticket/2e772eab-wrapper-contract-versioning-apm-wrapper…
philippepascal May 1, 2026
a6dc453
ticket(7f5f73d5): start — ready → in_progress
philippepascal May 1, 2026
7fa63fa
Merge remote-tracking branch 'origin/epic/4312fbd4-agent-wrapper-arch…
philippepascal May 1, 2026
8e94a2b
Add per-agent instruction resolution with 5-level chain
philippepascal May 1, 2026
1d9d70c
ticket(7f5f73d5): mark "When `[worker_profiles.<P>].instructions` is …
philippepascal May 1, 2026
5e0d3ad
ticket(7f5f73d5): mark "When `[worker_profiles.<P>].instructions` is …
philippepascal May 1, 2026
2c4fdca
ticket(7f5f73d5): mark "When neither profile nor global `[workers].in…
philippepascal May 1, 2026
bd9bc95
ticket(7f5f73d5): mark "When the first three levels all fail and agen…
philippepascal May 1, 2026
bf7d65a
ticket(7f5f73d5): mark "When all four levels fail (custom agent, no p…
philippepascal May 1, 2026
33c716d
ticket(7f5f73d5): mark "An existing project whose config has `[worker…
philippepascal May 1, 2026
2dfd015
ticket(7f5f73d5): mark "An existing project whose config has `[worker…
philippepascal May 1, 2026
baf7929
ticket(7f5f73d5): mark "`apm validate` reports a config error when `[…
philippepascal May 1, 2026
a059c04
ticket(7f5f73d5): mark "`apm validate` does not regress on the existi…
philippepascal May 1, 2026
33f87d5
ticket(7f5f73d5): mark "Both `apm.worker.md` and `apm.spec-writer.md`…
philippepascal May 1, 2026
0adf498
ticket(7f5f73d5): mark "The role (`worker` or `spec-writer`) is read …
philippepascal May 1, 2026
924d386
ticket(7f5f73d5): mark "Unit tests cover all five levels of the chain…
philippepascal May 1, 2026
7e6a3e6
ticket(7f5f73d5): in_progress → implemented
philippepascal May 1, 2026
e0110c2
Merge branch 'ticket/7f5f73d5-per-agent-instructions-resolution-under…
philippepascal May 1, 2026
d06f461
ticket(71d80e40): start — ready → in_progress
philippepascal May 1, 2026
ac068f2
Merge remote-tracking branch 'origin/epic/4312fbd4-agent-wrapper-arch…
philippepascal May 1, 2026
18d66e5
Add apm agents subcommand: list, new, test, eject
philippepascal May 1, 2026
7a31425
ticket(71d80e40): mark "apm agents list` prints a row for the `claude…
philippepascal May 1, 2026
f19e15d
ticket(71d80e40): mark "prints a row for each executable" in Acceptan…
philippepascal May 1, 2026
d9b2d9a
ticket(71d80e40): mark "marks the agent matching" in Acceptance criteria
philippepascal May 1, 2026
402c6b0
ticket(71d80e40): mark "shows a `parser` column" in Acceptance criteria
philippepascal May 1, 2026
813b3f4
ticket(71d80e40): mark "creates `.apm/agents/<name>/wrapper.sh` with …
philippepascal May 1, 2026
5bb732d
ticket(71d80e40): mark "creates `.apm/agents/<name>/apm.worker.md`" i…
philippepascal May 1, 2026
e973ed8
ticket(71d80e40): mark "creates `.apm/agents/<name>/apm.spec-writer.m…
philippepascal May 1, 2026
e79d329
ticket(71d80e40): mark "creates `.apm/agents/<name>/manifest.toml` co…
philippepascal May 1, 2026
5ba37f6
ticket(71d80e40): mark "exits non-zero with a message that mentions `…
philippepascal May 1, 2026
924c75c
ticket(71d80e40): mark "new <name> --force" in Acceptance criteria
philippepascal May 1, 2026
500ad77
ticket(71d80e40): mark "prints next-step guidance" in Acceptance crit…
philippepascal May 1, 2026
8ffa173
ticket(71d80e40): mark "exits 0 and prints a pass summary" in Accepta…
philippepascal May 1, 2026
6224bdb
ticket(71d80e40): mark "exits non-zero and prints a fail summary" in …
philippepascal May 1, 2026
b734d74
ticket(71d80e40): mark "reports exit code, canonical JSONL event coun…
philippepascal May 1, 2026
b600420
ticket(71d80e40): mark "exits non-zero with a clear error message" in…
philippepascal May 1, 2026
1f72b89
ticket(71d80e40): mark "eject claude" in Acceptance criteria
philippepascal May 1, 2026
1630df0
ticket(71d80e40): mark "eject <name>` creates `.apm/agents/<name>/man…
philippepascal May 1, 2026
c2f5aff
ticket(71d80e40): mark "sets the execute bit on the ejected" in Accep…
philippepascal May 1, 2026
27f203a
ticket(71d80e40): mark "exits non-zero when `.apm/agents/<name>/` alr…
philippepascal May 1, 2026
853a553
ticket(71d80e40): mark "exits non-zero with a message when `<name>` i…
philippepascal May 1, 2026
3d8650b
ticket(71d80e40): in_progress → implemented
philippepascal May 1, 2026
669db57
Merge branch 'ticket/71d80e40-apm-agents-subcommand-new-list-test-eje…
philippepascal May 1, 2026
3244b19
ticket(2803bf07): start — ready → in_progress
philippepascal May 1, 2026
514b5c1
Merge remote-tracking branch 'origin/epic/4312fbd4-agent-wrapper-arch…
philippepascal May 1, 2026
ac59e5f
Add external parser strategy for custom wrappers
philippepascal May 1, 2026
05e8e13
Check off acceptance criteria for 2803bf07
philippepascal May 1, 2026
3bf0f89
ticket(2803bf07): in_progress → implemented
philippepascal May 1, 2026
0aa1dff
Merge branch 'ticket/2803bf07-output-parser-strategy-external-parsers…
philippepascal May 1, 2026
16565c1
ticket(25c92daa): start — ready → in_progress
philippepascal May 1, 2026
8e1a45d
Merge remote-tracking branch 'origin/epic/4312fbd4-agent-wrapper-arch…
philippepascal May 1, 2026
b36d4c1
Add mock-happy, mock-sad, mock-random, debug built-in wrappers
philippepascal May 1, 2026
ee34f1f
ticket(25c92daa): set section Acceptance criteria
philippepascal May 1, 2026
ddafa82
ticket(25c92daa): in_progress → implemented
philippepascal May 1, 2026
d2be27d
Merge branch 'ticket/25c92daa-mock-and-debug-built-in-wrappers-mock-h…
philippepascal May 1, 2026
499a1a0
Wrapper epic cleanup: hardcoded "claude" uplift, mock tests, dedup
philippepascal May 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .apm/apm.spec-writer.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,3 +169,7 @@ transition to `question`. Do not guess and proceed.

Once an answer arrives, reflect the decision in `### Approach` before
transitioning back to `specd`.

---

**Frontmatter agent override** (supervisor tool): A supervisor may add `agent = "<name>"` or an `[agent_overrides]` table to a ticket's frontmatter to select a specific agent for that ticket or for individual profiles. Do not set these fields yourself — they are a supervisor-level escape hatch for debugging or per-ticket specialisation.
4 changes: 4 additions & 0 deletions .apm/apm.worker.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,3 +149,7 @@ in `apm show <id>` under Worktree — note it at the start of your run.

If a tool call resolves to a path outside your worktree, stop immediately, file
a side-note ticket, and set yourself to blocked.

---

**Frontmatter agent override** (supervisor tool): A supervisor may add `agent = "<name>"` or an `[agent_overrides]` table to a ticket's frontmatter to select a specific agent for that ticket or for individual profiles. Do not set these fields yourself — they are a supervisor-level escape hatch for debugging or per-ticket specialisation.
2 changes: 2 additions & 0 deletions .apm/workflow.toml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ instructions = ".apm/apm.spec-writer.md"
[[workflow.states.transitions]]
to = "specd"
trigger = "manual"
outcome = "success"

[[workflow.states.transitions]]
to = "question"
Expand All @@ -100,6 +101,7 @@ instructions = ".apm/apm.spec-writer.md"
[[workflow.states.transitions]]
to = "specd"
trigger = "manual"
outcome = "success"

[[workflow.states.transitions]]
to = "question"
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion EPIC.md
Original file line number Diff line number Diff line change
@@ -1 +1 @@
# apm help: auto-derived git-style topic help
# Agent wrapper architecture
323 changes: 323 additions & 0 deletions apm-core/src/agents.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,323 @@
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use anyhow::Result;
use crate::wrapper::{self, Wrapper, WrapperContext, WrapperKind};
use crate::wrapper::custom::CustomWrapper;
use crate::config::Config;

pub struct WrapperEntry {
pub name: String,
pub kind: WrapperKind,
pub parser: String,
pub configured_as: Vec<String>,
}

#[derive(Debug)]
pub struct TestReport {
pub exit_code: i32,
pub canonical_events: usize,
pub non_canonical_lines: usize,
pub stderr_lines: usize,
pub wall_millis: u64,
pub passed: bool,
}

const MANIFEST_TEMPLATE: &str =
"[wrapper]\ncontract_version = 1\nparser = \"canonical\"\n";

const WRAPPER_TEMPLATE: &str = r#"#!/usr/bin/env bash
# APM wrapper skeleton
#
# Environment variables provided by APM:
# APM_AGENT_NAME - name of this worker (from config)
# APM_TICKET_ID - 8-char hex ticket ID
# APM_TICKET_BRANCH - git branch for this ticket
# APM_TICKET_WORKTREE - absolute path to the ticket worktree
# APM_SYSTEM_PROMPT_FILE - path to a file containing the system prompt
# APM_USER_MESSAGE_FILE - path to a file containing the user message (ticket content)
# APM_SKIP_PERMISSIONS - "1" if --dangerously-skip-permissions should be passed; "0" otherwise
# APM_PROFILE - active worker profile name
# APM_ROLE_PREFIX - optional role label prepended to the worker identity
# APM_WRAPPER_VERSION - contract version this APM build implements (currently "1")
# APM_BIN - absolute path to the running apm binary
# APM_OPT_* - key-value options from [workers.options] in config.toml
#
# Contract:
# stdout - emit JSONL events (one JSON object per line, each with a "type" key)
# stderr - free-form log output (not parsed by APM)
# exit 0 - success; non-zero signals failure
#
set -euo pipefail

# Dump all APM_* env vars to stderr for debugging
env | grep '^APM_' >&2 || true

# Read inputs
SYSTEM_PROMPT="$(cat "$APM_SYSTEM_PROMPT_FILE")"
USER_MESSAGE="$(cat "$APM_USER_MESSAGE_FILE")"

# TODO: replace this printf with a real agent invocation that:
# 1. Sends SYSTEM_PROMPT + USER_MESSAGE to your AI tool
# 2. Emits JSONL events on stdout as the tool runs
printf '{"type":"text","text":"wrapper skeleton -- replace with real invocation"}\n'

# TODO: when the agent finishes, transition the ticket:
# apm state "$APM_TICKET_ID" <target-state>

exit 0
"#;

const CLAUDE_EJECT_SCRIPT: &str = r#"#!/usr/bin/env bash
# Ejected from APM built-in: claude
set -euo pipefail

ARGS=(--print --output-format stream-json --verbose)

ARGS+=(--system-prompt "$(cat "$APM_SYSTEM_PROMPT_FILE")")

if [[ -n "${APM_OPT_MODEL:-}" ]]; then
ARGS+=(--model "$APM_OPT_MODEL")
fi

if [[ "${APM_SKIP_PERMISSIONS:-0}" == "1" ]]; then
ARGS+=(--dangerously-skip-permissions)
fi

exec claude "${ARGS[@]}" "$(cat "$APM_USER_MESSAGE_FILE")"
"#;

const DEFAULT_WORKER_MD: &str = include_str!("default/apm.worker.md");
const DEFAULT_SPEC_WRITER_MD: &str = include_str!("default/apm.spec-writer.md");

fn rand_u16() -> u16 {
use std::time::{SystemTime, UNIX_EPOCH};
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.subsec_nanos() as u16
}

pub fn list_wrappers(root: &Path, config: &Config) -> Result<Vec<WrapperEntry>> {
let mut entries: Vec<WrapperEntry> = Vec::new();

// Built-in entries
for name in wrapper::list_builtin_names() {
entries.push(WrapperEntry {
name: name.to_string(),
kind: WrapperKind::Builtin(name.to_string()),
parser: "canonical".to_string(),
configured_as: vec![],
});
}

// Project entries from .apm/agents/
let agents_dir = root.join(".apm").join("agents");
if agents_dir.is_dir() {
let rd = match std::fs::read_dir(&agents_dir) {
Ok(rd) => rd,
Err(_) => return Ok(entries),
};
let mut names: Vec<String> = rd
.filter_map(|e| e.ok())
.filter(|e| e.path().is_dir())
.filter_map(|e| e.file_name().into_string().ok())
.collect();
names.sort();

for entry_name in names {
if let Ok(Some(WrapperKind::Custom { script_path, manifest })) =
wrapper::resolve_wrapper(root, &entry_name)
{
let parser = manifest
.as_ref()
.map(|m| m.parser.clone())
.unwrap_or_else(|| "canonical".to_string());
entries.push(WrapperEntry {
name: entry_name,
kind: WrapperKind::Custom { script_path, manifest },
parser,
configured_as: vec![],
});
}
}
}

// Configured marker: global [workers].agent plus per-profile [worker_profiles.*].agent.
let global_agent = config.workers.agent.as_deref().unwrap_or("claude").to_string();
for entry in &mut entries {
if entry.name == global_agent {
entry.configured_as.push("(configured)".to_string());
}
for (profile_name, profile) in &config.worker_profiles {
if let Some(ref agent) = profile.agent {
if entry.name == *agent {
entry.configured_as.push(format!("({profile_name})"));
}
}
}
}

Ok(entries)
}

pub fn scaffold_wrapper(root: &Path, name: &str, force: bool) -> Result<()> {
let dir = root.join(".apm").join("agents").join(name);
if dir.exists() && !force {
anyhow::bail!(".apm/agents/{name}/ already exists; use --force to overwrite");
}
std::fs::create_dir_all(&dir)?;

// Write wrapper.sh
let wrapper_path = dir.join("wrapper.sh");
std::fs::write(&wrapper_path, WRAPPER_TEMPLATE)?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(&wrapper_path, std::fs::Permissions::from_mode(0o755))?;
}

// Write manifest.toml
std::fs::write(dir.join("manifest.toml"), MANIFEST_TEMPLATE)?;

// Write apm.worker.md
let worker_md = std::fs::read_to_string(root.join(".apm").join("apm.worker.md"))
.unwrap_or_else(|_| DEFAULT_WORKER_MD.to_string());
std::fs::write(dir.join("apm.worker.md"), &worker_md)?;

// Write apm.spec-writer.md
let spec_writer_md =
std::fs::read_to_string(root.join(".apm").join("apm.spec-writer.md"))
.unwrap_or_else(|_| DEFAULT_SPEC_WRITER_MD.to_string());
std::fs::write(dir.join("apm.spec-writer.md"), &spec_writer_md)?;

Ok(())
}

pub fn test_wrapper(root: &Path, name: &str) -> Result<TestReport> {
let kind = wrapper::resolve_wrapper(root, name)?.ok_or_else(|| {
anyhow::anyhow!(
"agent '{}' not found: checked built-ins and .apm/agents/{}/",
name,
name
)
})?;

let tmp: PathBuf =
std::env::temp_dir().join(format!("apm-agents-test-{:04x}", rand_u16()));
std::fs::create_dir_all(&tmp)?;

let sys_file = tmp.join("system.txt");
let msg_file = tmp.join("message.txt");
let log_path = tmp.join("wrapper.log");

std::fs::write(&sys_file, "You are a test agent.")?;
std::fs::write(&msg_file, "Test run -- apm agents test.")?;

let ctx = WrapperContext {
worker_name: "agents-test".to_string(),
ticket_id: "00000000".to_string(),
ticket_branch: "test/agents-test".to_string(),
worktree_path: tmp.clone(),
system_prompt_file: sys_file,
user_message_file: msg_file,
skip_permissions: false,
profile: "test".to_string(),
role_prefix: None,
options: HashMap::new(),
model: None,
log_path: log_path.clone(),
container: None,
extra_env: HashMap::new(),
root: root.to_path_buf(),
keychain: HashMap::new(),
current_state: "test".to_string(),
};

let start = std::time::Instant::now();
let mut child = match kind {
WrapperKind::Custom { script_path, manifest } => {
CustomWrapper { script_path, manifest }.spawn(&ctx)?
}
WrapperKind::Builtin(n) => {
wrapper::resolve_builtin(&n)
.expect("registered builtin")
.spawn(&ctx)?
}
};

let status = child.wait()?;
let wall_millis = start.elapsed().as_millis() as u64;
let exit_code = status.code().unwrap_or(-1);

// Classify log lines
let log_content = std::fs::read_to_string(&log_path).unwrap_or_default();
let mut canonical_events = 0usize;
let mut non_canonical_lines = 0usize;
let mut stderr_lines = 0usize;

for line in log_content.lines() {
if line.is_empty() {
continue;
}
if line.starts_with("APM_") {
stderr_lines += 1;
} else if let Ok(val) = serde_json::from_str::<serde_json::Value>(line) {
if val.get("type").is_some() {
canonical_events += 1;
} else {
non_canonical_lines += 1;
}
} else {
non_canonical_lines += 1;
}
}

let passed = status.success() && canonical_events >= 1;
let report = TestReport {
exit_code,
canonical_events,
non_canonical_lines,
stderr_lines,
wall_millis,
passed,
};

let _ = std::fs::remove_dir_all(&tmp);

Ok(report)
}

pub fn eject_wrapper(root: &Path, name: &str) -> Result<()> {
if wrapper::resolve_builtin(name).is_none() {
anyhow::bail!(
"'{}' is not a known built-in; run apm agents list to see available wrappers",
name
);
}

let dir = root.join(".apm").join("agents").join(name);
if dir.exists() {
anyhow::bail!(".apm/agents/{name}/ already exists; delete it first to eject again");
}

std::fs::create_dir_all(&dir)?;

let script_content = match name {
"claude" => CLAUDE_EJECT_SCRIPT,
other => anyhow::bail!("eject not yet implemented for built-in {}", other),
};
let script_path = dir.join("wrapper.sh");
std::fs::write(&script_path, script_content)?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(&script_path, std::fs::Permissions::from_mode(0o755))?;
}

// Write manifest.toml — intentionally the same template as scaffold_wrapper:
// recognised as v1-canonical by 2c32a282's manifest parser and 2e772eab's version check,
// so the ejected script requires no extra setup.
std::fs::write(dir.join("manifest.toml"), MANIFEST_TEMPLATE)?;

Ok(())
}
Loading
Loading