1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
//! Instruction merge pipeline — compose a session's launch instructions.
//!
//! Why: every Claude Code session trusty-mpm starts must receive the same
//! framework instructions and the dynamic delegation routing context; doing
//! that merge ad-hoc at each launch site invites drift in ordering and
//! content.
//! What: [`build_instructions`] loads `INSTRUCTIONS.md`, generates the
//! delegation authority section from the deployed agents, and concatenates
//! the two sections in the fixed order 3 → 4 (framework → delegation). It
//! also seeds a project `CLAUDE.md` stub as a side effect (so a fresh
//! workspace always has a place for project notes), but — as of the #382 fix
//! — that project-notes content was never actually part of the text
//! delivered to `claude --append-system-prompt-file` (the real launch prompt
//! comes from [`crate::core::instruction_overrides::resolve_pm_prompt`] /
//! [`crate::core::session_launch::build_system_prompt_for`], neither of which
//! reads `PipelineOutput::merged`). Concatenating `CLAUDE.md` into `merged`
//! was therefore dead code that only risked a FUTURE accidental re-wiring of
//! a duplicate prompt payload (Claude Code already memory-loads `CLAUDE.md`
//! natively); it has been removed so `merged` cannot silently regrow that
//! duplication.
//! Test: `cargo test -p trusty-mpm-core instruction_pipeline` covers the
//! merge, a missing `INSTRUCTIONS.md`, and stub creation.
use fmt;
use PathBuf;
use crate;
use crateSectionId;
/// Separator placed between merged instruction sections.
///
/// Why: the override resolver in [`crate::core::instruction_overrides`] must use
/// the identical rule so the bundled and override-resolved prompts never
/// visually diverge.
/// What: the `\n\n---\n\n` Markdown horizontal rule used between every section.
/// Test: `separators_are_consistent` in `instruction_overrides`.
pub const SECTION_SEPARATOR: &str = "\n\n---\n\n";
// ---------------------------------------------------------------------------
// Bundled system-prompt assembly
//
// Why: trusty-mpm must own its own PM instructions rather than reading from a
// `~/.claude-mpm/` install at runtime. The section assets below are embedded at
// compile time and assembled into a single `INSTRUCTIONS.md` that is passed to
// `claude --append-system-prompt-file` on every session launch.
//
// SOURCE OF TRUTH (#4183): one markdown file per [`SectionId`], under
// `assets/instructions/sections/`. The four monolithic assets this crate used to
// embed (`PM_INSTRUCTIONS.md`, `WORKFLOW.md`, `AGENT_DELEGATION.md`,
// `BASE_PM.md`) are gone; `pm_instructions()` and `base_pm()` reconstitute the
// two that spanned several sections so the legacy override assembly keeps its
// exact shape. Nothing is duplicated between the two models, so the composed
// package and the legacy assembly cannot drift apart in content — the property
// that makes `composed_package_is_byte_identical_to_the_legacy_bundled_fallback`
// meaningful rather than a check of two copies of the same paste.
// ---------------------------------------------------------------------------
/// Absorbed BASE_PM `## Identity` — who the PM is. Floor, tier `fixed`.
pub const SECTION_IDENTITY: &str =
include_str!;
/// The PM's core operating instructions. Tier `project`.
pub const SECTION_CORE: &str = include_str!;
/// Memory (context-first) protocol guidance. Tier `project`.
pub const SECTION_MEMORY: &str = include_str!;
/// Code/architecture search protocol guidance. Tier `project`.
pub const SECTION_SEARCH: &str = include_str!;
/// 5-phase workflow execution details, including the sprint/harden doctrine.
///
/// `pub(crate)` so the override resolver can use it when no `WORKFLOW.md`
/// override is present.
pub const WORKFLOW: &str = include_str!;
/// Agent delegation routing doctrine (the live roster is appended at compose
/// time, never authored here).
///
/// `pub(crate)` so the override resolver can use it when no
/// `AGENT_DELEGATION.md` override is present.
pub const AGENT_DELEGATION: &str =
include_str!;
/// The canonical Prohibitions and Circuit Breakers tables. Floor, tier `fixed`.
///
/// Split out of `core.md` by #4573: both tables sat inside the `project`-tier
/// core section, so a three-line `CORE` block in a project's `CLAUDE.md` deleted
/// the PM's entire delegation-enforcement authority and still validated.
pub const SECTION_ENFORCEMENT: &str =
include_str!;
/// Absorbed BASE_PM non-overridable rules, the customization contract, and the
/// Trusty tool-priority mandate. Floor, tier `fixed`.
pub const SECTION_NON_OVERRIDABLE_RULES: &str =
include_str!;
/// Absorbed BASE_PM framework-guaranteed conventions. Floor, tier `fixed`.
pub const SECTION_FRAMEWORK_CONVENTIONS: &str =
include_str!;
/// The compile-time table a schema-v2 `file` body resolves through.
///
/// Why: the instruction manifest (#4318) names its prose by path
/// (`{"kind":"file","path":"sections/core.md"}`) so the bulk of the instructions
/// keeps living in reviewable markdown rather than becoming one 23 KB JSON line
/// — but a path resolved at *runtime* would put the delivered system prompt at
/// the mercy of the filesystem and would let a renamed section ship as a silent
/// content drop. Every entry here is an `include_str!` of a constant declared
/// above, so the build stays hermetic and a missing section file is a compile
/// error rather than a launch-time surprise.
/// What: the nine canonical section sources, keyed by the path form the manifest
/// uses — relative to `assets/instructions/`. Table order is irrelevant; the
/// manifest's `blocks` array alone decides emission order.
/// Test: `every_section_source_resolves`, `unknown_file_source_is_rejected`.
pub const SECTION_SOURCES: = ;
/// Resolve a manifest `file` body path to its embedded source.
///
/// Why: one lookup point means a path typo in the manifest becomes a named
/// [`crate::core::instruction_package::ValidationError::UnknownFileSource`]
/// instead of an empty block.
/// What: a linear scan of [`SECTION_SOURCES`] — nine entries, called a handful
/// of times per process, so a map would buy nothing and would reintroduce the
/// iteration-order hazard the package format exists to avoid.
/// Test: `every_section_source_resolves`, `unknown_file_source_is_rejected`.
pub
/// The former `PM_INSTRUCTIONS.md` body, rebuilt from its three sections.
///
/// Why: the legacy override assembly
/// ([`crate::core::instruction_overrides::assemble_sections`]) treats the PM body
/// as one section it may be fully replaced by `PM_INSTRUCTIONS_DEPLOYED.md`. It
/// still needs that single string, but the *authored* source is now three files.
/// Reconstituting here — rather than keeping a fourth copy on disk — is what
/// stops the legacy path and the packaged path from delivering different
/// content once a section is edited (#4183).
/// What: Core, Memory and Search joined with a paragraph break, in that order,
/// with the trailing newline a file would have carried. The paragraph break is
/// deliberately [`crate::core::instruction_package::Join::Blank`]'s literal, so
/// this string is byte-identical to what the packaged composer emits for the
/// same three blocks.
/// Test: `pm_instructions_is_its_three_sections`,
/// `composed_package_is_byte_identical_to_the_legacy_bundled_fallback`.
pub
/// Project a run of sections out of the bundled manifest.
///
/// Why: since #4318 the manifest may author a rule inline rather than in a
/// section file, so rebuilding these strings from the `include_str!` constants
/// would deliver that rule to package-composed sessions and silently withhold it
/// from the legacy assembly and from [`assemble_system_prompt`]. Projecting the
/// manifest keeps one source of truth for the *content*, while the constants stay
/// as the retained fallback for the case where the manifest itself is unreadable.
/// What: [`crate::core::bundled_pm_package::authored_run`], or `None` when the
/// manifest failed to parse or validate.
/// Test: `pm_instructions_is_its_three_sections`, `base_pm_is_its_four_sections`.
/// The bundled workflow section, as authored in the manifest.
///
/// Why: the workflow section is no longer only `sections/workflow.md` — the
/// manifest appends the opportunistic-fix rule as its own block. Every consumer
/// must therefore ask the manifest, not the constant, or a project with a
/// `WORKFLOW.md` override would be the only one to notice the difference.
/// What: the authored workflow blocks joined as declared, trimmed; the raw
/// `WORKFLOW` constant when the manifest is unreadable.
/// Test: `workflow_section_carries_the_opportunistic_fix_rule`,
/// `assemble_system_prompt_contains_all_sections`.
pub
/// The bundled delegation doctrine plus the roster-precedence note.
///
/// Why: #4318 moved the note's prose out of a Rust constant and into the manifest,
/// where its position between the doctrine and the live roster is declared rather
/// than formatted in by hand at two call sites.
/// What: the authored `agent-delegation` blocks joined as declared — the section
/// source followed by the note — trimmed. Falls back to the doctrine alone if the
/// manifest is unreadable, which is the pre-#4069 shape.
/// Test: `bundled_delegation_appends_deployed_roster`,
/// `composed_prompt_carries_the_live_roster_and_the_precedence_note`.
pub
/// The non-overridable framework floor, rebuilt from its three sections.
///
/// Why: the floor is appended last under *every* override branch, including full
/// PM replacement, so the resolver needs it as one opaque string. Same
/// no-duplicate-copy argument as [`pm_instructions`].
/// What: Identity, Enforcement (the Prohibitions and Circuit Breakers tables),
/// Non-Overridable Rules (which now carries the Trusty tool-priority mandate) and
/// Framework-Guaranteed Conventions, joined with a paragraph break.
///
/// #4573: `Enforcement` joins the floor here so the two authority tables reach
/// EVERY legacy branch too — including the `PM_INSTRUCTIONS_DEPLOYED.md` full
/// replacement, which discards every body section and appends only this string.
/// That is the second of the two wholesale-deletion paths #4573 names, and it is
/// closed by this list, not by the tier declaration alone.
///
/// ORDER CHANGE, recorded because it is the one floor reordering #4183 makes:
/// `BASE_PM.md` used to place `## Trusty Tool Priority (Non-Overridable)` *after*
/// `## Framework-Guaranteed Conventions`. It is a non-overridable rule, so it now
/// travels with the other non-overridable rules and consequently precedes the
/// conventions. Position only — not one word of either block changed, and both
/// remain inside the floor, so nothing about what is overridable moved.
/// Test: `base_pm_is_its_four_sections`, `floor_carries_the_tool_priority_mandate`.
pub
/// Assemble the full system prompt from bundled source components.
///
/// Why: a launched `claude` session must receive identical, version-controlled
/// PM instructions every time; embedding the sources and joining them here
/// removes any dependency on an external `~/.claude-mpm/` install.
/// What: concatenates the bundled sections in the fixed order
/// PM body → WORKFLOW → AGENT_DELEGATION → floor, separated by a `---` rule. The
/// floor comes last as the non-overridable framework floor; it carries the
/// Trusty MCP tool-priority block.
/// Test: `assemble_system_prompt_contains_all_sections`.
/// Write the assembled system prompt to an explicit path.
///
/// Why: `tm install` must be testable against a temp directory rather than the
/// real `~/.trusty-mpm`; extracting the path-parameterised write here lets both
/// the production path ([`install_system_prompt`]) and the install handler use
/// the same assembly logic without touching the real home during unit tests.
/// What: creates parent directories if absent, then writes
/// [`assemble_system_prompt`] to `dest`.
/// Test: `install_system_prompt_to_writes_assembled`, `install_writes_assembled_prompt`.
/// Write the assembled system prompt to the trusty-mpm framework directory.
///
/// Why: `tm launch` passes `~/.trusty-mpm/framework/instructions/INSTRUCTIONS.md`
/// to `claude --append-system-prompt-file`; this regenerates that file from the
/// bundled assets so it always reflects the current trusty-mpm build.
/// What: creates `~/.trusty-mpm/framework/instructions/` if needed and writes
/// the output of [`assemble_system_prompt`] to `INSTRUCTIONS.md`, returning the
/// path it wrote. Delegates the write to [`install_system_prompt_to`].
/// Test: `install_system_prompt_writes_file`.
/// The CLAUDE.md stub seeded into a project on first session start.
///
/// Why: the project needs a place for project-specific notes; trusty-mpm
/// creates it exactly once and then never touches it again, so the operator
/// can edit freely (issue #2170 — trusty-mpm must never modify a target
/// project's `CLAUDE.md`).
pub const CLAUDE_MD_STUB: &str = "# Project Instructions
<!-- trusty-mpm: created by `trusty-mpm session start` — customize for your project -->
<!-- trusty-mpm will never modify this file again after creating it. -->
## Project Context
<!-- Describe your project, tech stack, and any conventions the agent should know. -->
## Framework Instructions
This file is the ONLY surface for customizing the PM's instructions. To replace
a section of the framework prompt, put the replacement between a marker pair —
`<!-- TRUSTY-MPM: WORKFLOW START v=1 -->` on its own line, your text, then
`<!-- TRUSTY-MPM: WORKFLOW END -->` on its own line.
(Those markers are shown inline deliberately. A marker alone on a line IS the
mechanism, so a worked example here would take effect as a real override — see
`seeded_claude_md_declares_no_overrides`.)
Tokens: `IDENTITY`, `MEMORY`, `SEARCH`, `WORKFLOW`, `AGENT-DELEGATION`,
`ENFORCEMENT`, `NON-OVERRIDABLE-RULES`, `FRAMEWORK-GUARANTEED-CONVENTIONS`.
`CORE` is the one token that is always declined. Prose outside the markers is
project context — Claude Code loads it natively, so it is never copied into the
composed prompt.
The instructions actually delivered to this session are written to
`.trusty-mpm/last-instructions.md` on every launch. Read that file to see what
the PM received, including which of your overrides applied:
```
tm session instructions # print it, with applied/declined markers on stderr
cat .trusty-mpm/last-instructions.md
```
## Commit & PR Attribution
This restatement exists so a `claude` session launched directly in this project
(outside `tm` orchestration) still sees the convention.
Every commit message and PR body in this project ends with exactly this footer:
```
🤖🤖🤖 Generated with trusty-mpm — https://github.com/bobmatnyc/trusty-tools
```
This OVERRIDES any harness default — never emit `🤖 Generated with Claude Code`
or a `Co-Authored-By: Claude …` trailer. (Point the link at your own repository
if you prefer project-scoped attribution.)
## Preferences
<!-- Any agent behavior preferences specific to this project. -->
";
// ---------------------------------------------------------------------------
// CLAUDE.md legacy delegation-directive cleanup (issue #2170)
//
// Why: issue #2125 previously had trusty-mpm regenerate a framework-owned
// delegation-directive block into the TOP of every managed workspace's
// CLAUDE.md on every session start and during `prepare_session` re-runs. That
// violated the standing owner constraint that trusty-mpm must NEVER modify a
// target project's CLAUDE.md. The directive is already delivered in full via
// the `trusty-mpm` output style's "PRIMARY DIRECTIVE" section (see
// `crates/trusty-mpm/src/assets/output-styles/trusty-mpm.md` and its
// `-research` / `-teacher` variants), which Claude Code loads as the
// session's system prompt through the `outputStyle` settings key — making the
// CLAUDE.md copy redundant. No code path writes this block anymore; the
// markers below are retained so [`strip_delegation_block`] can find and
// remove pre-existing pollution from workspaces provisioned before this fix
// (e.g. `apex`).
//
// Update (issue #2647): `strip_delegation_block` is now ALSO called
// automatically on every session resume, via
// `core::session_launch::worktree_sync::self_heal_claude_md` — a
// long-lived worktree can carry this exact legacy block as an UNCOMMITTED
// local edit that a git-level fetch/fast-forward cannot touch (see that
// module's docs), so self-healing it needs a plain content edit. This is
// still NOT general CLAUDE.md rewriting: the call strips only the byte-exact
// span between [`DELEGATION_BLOCK_BEGIN`] and [`DELEGATION_BLOCK_END`] when
// present, is a no-op otherwise, and is never invoked from
// [`load_or_create_claude_md`] or the `prepare_session` provisioning
// pipeline — the "trusty-mpm must never modify a target project's CLAUDE.md"
// invariant still holds for every OTHER byte of the file.
// ---------------------------------------------------------------------------
/// Begin marker fencing the legacy framework-owned delegation-directive block
/// that trusty-mpm injected into project `CLAUDE.md` files prior to issue
/// #2170.
///
/// What: an HTML-comment marker, invisible in rendered Markdown and harmless
/// to Claude Code's raw-text reading. Used only by [`strip_delegation_block`]
/// to locate legacy pollution — nothing writes it anymore.
/// Test: `strip_delegation_block_removes_legacy_block`.
const DELEGATION_BLOCK_BEGIN: &str = "<!-- trusty-mpm:delegation-directive:begin \
(framework-owned — do not edit; regenerated on every session start, see issue #2125) -->";
/// End marker paired with [`DELEGATION_BLOCK_BEGIN`].
const DELEGATION_BLOCK_END: &str = "<!-- trusty-mpm:delegation-directive:end -->";
/// One-time cleanup helper: remove a legacy trusty-mpm delegation-directive
/// block from `content`, if present.
///
/// Why: workspaces provisioned before issue #2170 landed may still carry the
/// block trusty-mpm used to inject, fenced by [`DELEGATION_BLOCK_BEGIN`] /
/// [`DELEGATION_BLOCK_END`]. This function is still NOT called from
/// [`load_or_create_claude_md`] or the `prepare_session` provisioning
/// pipeline — trusty-mpm must never rewrite a project's `CLAUDE.md` as part
/// of normal provisioning. As of issue #2647 it IS called from one other
/// site: `core::session_launch::worktree_sync::self_heal_claude_md`, on
/// every session resume — a targeted, anchored self-heal (this exact fenced
/// span only) rather than the general rewriting the invariant above still
/// forbids. It is also available for an operator (or a future explicit,
/// opt-in cleanup command) to call on demand.
/// What: if both fence markers are present (in order), removes the span
/// between them (inclusive) plus the blank line that follows it, leaving
/// every other byte of `content` untouched. Returns `content` unchanged when
/// no fenced block is found.
/// Test: `strip_delegation_block_removes_legacy_block`,
/// `strip_delegation_block_noop_when_absent`.
/// Inputs to the instruction merge pipeline.
///
/// Why: bundles the three source locations so callers pass one value instead
/// of three loosely-related paths.
/// What: the framework `INSTRUCTIONS.md`, the project the roster is resolved
/// for, and the project `CLAUDE.md`.
/// Test: every `pipeline_*` test constructs one of these.
/// Result of a successful instruction merge.
///
/// Why: callers need both the composed text and a few flags describing what
/// happened (was a stub created? was `INSTRUCTIONS.md` present?) so they can
/// report it to the operator.
/// What: the merged instruction text plus per-source status flags.
/// Test: asserted by every `pipeline_*` test.
/// A failure raised while composing session launch instructions.
///
/// Why: the pipeline performs filesystem I/O (reading `CLAUDE.md`, creating
/// the stub and its parent directory); callers need a typed failure surface.
/// What: wraps the underlying IO error with the path that triggered it.
/// Test: not exercised by the happy-path tests; surfaced if a path is invalid.
/// Compose the effective session launch instructions.
///
/// Why: every CC session needs consistent framework instructions + dynamic
/// routing context. It also needs the project `CLAUDE.md` to exist (Claude
/// Code loads it natively), so this still seeds the stub as a side effect.
///
/// What: loads INSTRUCTIONS.md (falls back to empty string if missing),
/// generates delegation authority from the roster
/// [`crate::core::delegation_authority::resolve_roster`] resolves for
/// `project_dir`, concatenates the two in order 3→4, and returns the merged
/// string. Separately ensures the project `CLAUDE.md` exists (creating the stub
/// if absent) purely for its side effect — its content is intentionally NOT
/// folded into `merged` (dead code removed; see module docs).
///
/// Test: `pipeline_full`, `pipeline_missing_instructions`,
/// `pipeline_creates_claude_md`,
/// `session_start_count_matches_the_delivered_delegation_roster`
/// Load `CLAUDE.md`, seeding the stub (and parent directories) if it does not
/// exist. A pre-existing `CLAUDE.md` is read back byte-identical and never
/// written to (issue #2170 — trusty-mpm must never modify a target project's
/// `CLAUDE.md`).
///
/// Why: section 5 is entirely project-owned; trusty-mpm creates the stub
/// exactly once, on first session start, so the operator always has a place
/// for project-specific notes, and never touches the file again afterward.
/// What: reads the existing file and returns it unchanged; if absent, writes
/// [`CLAUDE_MD_STUB`] (creating parent directories as needed) and returns
/// that. Returns the final content and whether this call created the file.
/// Test: `pipeline_creates_claude_md`, `pipeline_claude_md_left_byte_identical`.
/// Concatenate the two instruction sections in the fixed 3 → 4 order.
///
/// Why: the merge order is part of the framework contract; isolating it keeps
/// the ordering rule in one auditable place. A third "project CLAUDE.md"
/// section previously existed here (dead code removed — see module docs):
/// Claude Code loads `CLAUDE.md` natively and the real launch prompt never
/// read this function's output for that content.
/// What: joins framework and delegation authority with a `---` rule, skipping
/// empty sections so a missing `INSTRUCTIONS.md` does not leave a dangling
/// separator.
/// Test: `pipeline_full`, `pipeline_missing_instructions`.