@sjawhar/opencode-legion-envoy 4.1.1 → 5.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -14394,168 +14394,6 @@ var EnvelopeSchema = exports_external.object({
14394
14394
  });
14395
14395
  var MINTED_DEDUPE_KEY_PATTERN = "^(?:envoy\\.role\\.forward\\.)?(?:publish|agent\\.[^.]+)\\.(?:[0-9a-f]{32}|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$";
14396
14396
  var mintedDedupeKey = new RegExp(MINTED_DEDUPE_KEY_PATTERN);
14397
- // ../contracts/src/handoff-schema.ts
14398
- var HANDOFF_SCHEMA_VERSION = 1;
14399
- var HANDOFF_PHASES = ["architect", "plan", "implement", "test", "review"];
14400
- var PLAN_REVIEW_MAX_ROUNDS = 3;
14401
- var PLAN_REVIEW_VERDICTS = ["approved", "rejected", "failed"];
14402
- var isoTimestamp = exports_external.string().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/);
14403
- var handoffPhase = exports_external.enum(HANDOFF_PHASES);
14404
- var nonEmpty = exports_external.string().trim().min(1);
14405
- var proofSchema = exports_external.object({
14406
- criterion: nonEmpty,
14407
- surface: nonEmpty,
14408
- command: nonEmpty,
14409
- observed: nonEmpty,
14410
- headSha: nonEmpty,
14411
- negativeControl: nonEmpty
14412
- }).passthrough();
14413
- var routingHintsSchema = exports_external.object({
14414
- skipArchitect: exports_external.boolean().optional(),
14415
- complexity: exports_external.enum(["trivial", "small", "medium", "large"]).optional(),
14416
- estimatedImplementers: exports_external.number().optional()
14417
- }).passthrough().optional();
14418
- var baseHandoffSchema = exports_external.object({
14419
- schemaVersion: exports_external.literal(HANDOFF_SCHEMA_VERSION),
14420
- phase: handoffPhase,
14421
- completed: isoTimestamp,
14422
- learningsInjected: exports_external.array(exports_external.string()).optional(),
14423
- learningsHelpful: exports_external.array(exports_external.string()).optional()
14424
- }).passthrough();
14425
- var architectSchema = baseHandoffSchema.extend({
14426
- phase: exports_external.literal("architect"),
14427
- scope: exports_external.enum(["trivial", "small", "medium", "large"]).optional(),
14428
- components: exports_external.array(exports_external.string()).optional(),
14429
- subIssues: exports_external.array(exports_external.string()).optional(),
14430
- routingHints: routingHintsSchema,
14431
- concerns: exports_external.array(exports_external.string()).optional()
14432
- });
14433
- var requiredSkillsSchema = exports_external.object({
14434
- implement: exports_external.array(exports_external.string()).optional(),
14435
- test: exports_external.array(exports_external.string()).optional(),
14436
- review: exports_external.array(exports_external.string()).optional()
14437
- }).passthrough().optional();
14438
- var gapAnalysisSchema = exports_external.object({
14439
- findings: exports_external.array(exports_external.object({ finding: exports_external.string(), answer: exports_external.string() }).passthrough()).optional(),
14440
- error: exports_external.string().optional()
14441
- }).passthrough().optional();
14442
- var planReviewSchema = exports_external.object({
14443
- verdict: exports_external.enum(PLAN_REVIEW_VERDICTS),
14444
- rounds: exports_external.number(),
14445
- remainingIssues: exports_external.array(exports_external.object({ issue: exports_external.string(), evidence: exports_external.string() }).passthrough()).optional(),
14446
- error: exports_external.string().optional()
14447
- }).passthrough().optional();
14448
- var planSchema = baseHandoffSchema.extend({
14449
- phase: exports_external.literal("plan"),
14450
- taskCount: exports_external.number().optional(),
14451
- independentTasks: exports_external.number().optional(),
14452
- routingHints: routingHintsSchema,
14453
- concerns: exports_external.array(exports_external.string()).optional(),
14454
- workflowRecommendation: exports_external.string().optional(),
14455
- requiredSkills: requiredSkillsSchema,
14456
- gapAnalysis: gapAnalysisSchema,
14457
- planReview: planReviewSchema
14458
- });
14459
- var implementSchema = baseHandoffSchema.extend({
14460
- phase: exports_external.literal("implement"),
14461
- filesChanged: exports_external.array(exports_external.string()).optional(),
14462
- proof: exports_external.array(proofSchema).min(1),
14463
- trickyParts: exports_external.array(exports_external.string()).optional(),
14464
- deviations: exports_external.array(exports_external.string()).optional(),
14465
- openQuestions: exports_external.array(exports_external.string()).optional(),
14466
- subPlanningNeeded: exports_external.boolean().optional(),
14467
- discoveredComplexity: exports_external.array(exports_external.string()).optional(),
14468
- suggestedSubWorkers: exports_external.number().optional()
14469
- });
14470
- var testSchema = baseHandoffSchema.extend({
14471
- phase: exports_external.literal("test"),
14472
- passed: exports_external.number().optional(),
14473
- failed: exports_external.number().optional(),
14474
- failures: exports_external.array(exports_external.object({ criterion: exports_external.string(), evidence: exports_external.string() }).passthrough()).optional(),
14475
- implementerProof: exports_external.object({ verdict: exports_external.enum(["verified", "rejected"]), how: nonEmpty }).passthrough(),
14476
- proof: exports_external.array(proofSchema).min(1).optional(),
14477
- documentationFeedback: exports_external.string().optional(),
14478
- observations: exports_external.array(exports_external.string()).optional()
14479
- }).refine((handoff) => (handoff.failures?.length ?? 0) > 0 || (handoff.failed ?? 0) > 0 || (handoff.proof?.length ?? 0) > 0, {
14480
- path: ["proof"],
14481
- message: "a passing test handoff needs the tester's own production-like proof"
14482
- }).refine((handoff) => (handoff.failed ?? 0) === 0 || (handoff.failures?.length ?? 0) > 0, {
14483
- path: ["failures"],
14484
- message: "a test handoff that reports failed > 0 records at least one failure"
14485
- }).refine((handoff) => handoff.implementerProof.verdict !== "rejected" || (handoff.failures?.length ?? 0) > 0, {
14486
- path: ["failures"],
14487
- message: "a rejected implementer proof is a recorded failure"
14488
- });
14489
- var reviewSchema = baseHandoffSchema.extend({
14490
- phase: exports_external.literal("review"),
14491
- critical: exports_external.number().optional(),
14492
- important: exports_external.number().optional(),
14493
- minor: exports_external.number().optional(),
14494
- verdict: exports_external.enum(["approved", "changes_requested"]).optional(),
14495
- keyFindings: exports_external.array(exports_external.object({ severity: exports_external.string(), file: exports_external.string(), description: exports_external.string() }).passthrough()).optional()
14496
- });
14497
- var nonEmptySkillList = exports_external.array(nonEmpty).min(1);
14498
- var recorded = (shape, whatToRecord) => exports_external.object(shape, {
14499
- error: (issue2) => issue2.input === undefined ? `missing \u2014 record ${whatToRecord}` : undefined
14500
- }).passthrough();
14501
- var gapAnalysisWriteSchema = recorded({
14502
- findings: exports_external.array(exports_external.object({ finding: nonEmpty, answer: nonEmpty }).passthrough()).optional(),
14503
- error: nonEmpty.optional()
14504
- }, "the gap analyst's `findings`, each with how the plan answers it (`[]` when it found none), or its failed call's `error`").refine((analysis) => analysis.findings === undefined !== (analysis.error === undefined), {
14505
- message: "record exactly one of `findings` or the failed call's `error`"
14506
- });
14507
- var planReviewWriteSchema = recorded({
14508
- verdict: exports_external.enum(PLAN_REVIEW_VERDICTS),
14509
- rounds: exports_external.number().int().min(1).max(PLAN_REVIEW_MAX_ROUNDS),
14510
- remainingIssues: exports_external.array(exports_external.object({ issue: nonEmpty, evidence: nonEmpty }).passthrough()).optional(),
14511
- error: nonEmpty.optional()
14512
- }, "the plan review's `verdict` and `rounds`, with `remainingIssues` when it was rejected or `error` when a review's call failed").superRefine((review, ctx) => {
14513
- const remaining = review.remainingIssues?.length ?? 0;
14514
- if (review.verdict === "rejected" && remaining === 0) {
14515
- ctx.addIssue({
14516
- code: "custom",
14517
- path: ["remainingIssues"],
14518
- message: "a rejected review records the blocking issues its last round named"
14519
- });
14520
- }
14521
- if (review.verdict === "rejected" && review.rounds < PLAN_REVIEW_MAX_ROUNDS) {
14522
- ctx.addIssue({
14523
- code: "custom",
14524
- path: ["rounds"],
14525
- message: `a review still rejecting after ${review.rounds} of ${PLAN_REVIEW_MAX_ROUNDS} rounds is revised and reviewed again, not recorded`
14526
- });
14527
- }
14528
- if (review.verdict === "approved" && remaining > 0) {
14529
- ctx.addIssue({
14530
- code: "custom",
14531
- path: ["remainingIssues"],
14532
- message: "an approved review leaves no blocking issue standing"
14533
- });
14534
- }
14535
- if (review.verdict === "failed" !== (review.error !== undefined)) {
14536
- ctx.addIssue({
14537
- code: "custom",
14538
- path: ["error"],
14539
- message: "a failed review records its call's error, and only a failed review does"
14540
- });
14541
- }
14542
- });
14543
- var planWriteSchema = planSchema.extend({
14544
- requiredSkills: exports_external.object({
14545
- implement: nonEmptySkillList,
14546
- test: nonEmptySkillList,
14547
- review: nonEmptySkillList
14548
- }).passthrough(),
14549
- gapAnalysis: gapAnalysisWriteSchema,
14550
- planReview: planReviewWriteSchema
14551
- });
14552
- var phaseHandoffSchema = exports_external.discriminatedUnion("phase", [
14553
- architectSchema,
14554
- planSchema,
14555
- implementSchema,
14556
- testSchema,
14557
- reviewSchema
14558
- ]);
14559
14397
  // ../contracts/src/subject.ts
14560
14398
  var AGENT_TOPIC_PREFIX = "notifications.agent.";
14561
14399
  var DISPATCH_TOPIC_PREFIX = "notifications.dispatch.";
@@ -14570,283 +14408,6 @@ function dispatchDocumentSubject(project, slug, type) {
14570
14408
  function agentSubject(session) {
14571
14409
  return `${AGENT_TOPIC_PREFIX}${session}`;
14572
14410
  }
14573
-
14574
- // ../contracts/src/legion-roles.ts
14575
- var LEGION_ROLES = [
14576
- "architect",
14577
- "planner",
14578
- "implementer",
14579
- "tester",
14580
- "reviewer",
14581
- "merger"
14582
- ];
14583
-
14584
- // ../contracts/src/legion-daemon-api.ts
14585
- var nonEmptyString = exports_external.string().min(1);
14586
- var appLogin = exports_external.string().regex(/^[^[\]]+\[bot\]$/);
14587
- var legionRole = exports_external.enum(LEGION_ROLES);
14588
- var requiredUnknown = exports_external.unknown().refine((value) => value !== undefined, {
14589
- message: "Required"
14590
- });
14591
- var architectCapability = exports_external.strictObject({
14592
- tree: nonEmptyString,
14593
- sessionId: nonEmptyString,
14594
- secret: nonEmptyString
14595
- });
14596
- var controllerIssue = exports_external.strictObject({
14597
- secret: nonEmptyString,
14598
- issue: nonEmptyString
14599
- });
14600
- var TREE_STATUSES = ["queued", "active", "lingering", "dead", "launch-failed", "closed"];
14601
- var stateTmuxLocator = exports_external.strictObject({
14602
- runtime: exports_external.literal("tmux"),
14603
- tmuxSession: nonEmptyString,
14604
- tmuxWindowId: nonEmptyString,
14605
- tmuxPaneId: nonEmptyString.optional()
14606
- });
14607
- var stateK8sLocator = exports_external.strictObject({
14608
- runtime: exports_external.literal("kubernetes"),
14609
- namespace: nonEmptyString,
14610
- podName: nonEmptyString,
14611
- podUid: nonEmptyString,
14612
- pvcName: nonEmptyString
14613
- });
14614
- var stateLocator = exports_external.discriminatedUnion("runtime", [stateTmuxLocator, stateK8sLocator]);
14615
- var stateTreeLocator = exports_external.discriminatedUnion("runtime", [
14616
- stateTmuxLocator.extend({ ompSessionFile: nonEmptyString.optional() }),
14617
- stateK8sLocator.extend({ ompSessionFile: nonEmptyString.optional() })
14618
- ]);
14619
- var stateExternalControllerLocator = exports_external.strictObject({
14620
- runtime: exports_external.literal("kubernetes"),
14621
- external: exports_external.literal(true),
14622
- sessionId: nonEmptyString,
14623
- registeredAt: exports_external.number().int().nonnegative()
14624
- });
14625
- var stateIssue = exports_external.strictObject({
14626
- key: nonEmptyString,
14627
- title: exports_external.string(),
14628
- status: exports_external.enum(ISSUE_STATUSES).optional(),
14629
- children: exports_external.array(nonEmptyString),
14630
- parent: nonEmptyString.optional(),
14631
- lastAppliedSeq: exports_external.number().int().nonnegative().optional()
14632
- });
14633
- var stateWorkspaceLost = exports_external.strictObject({
14634
- at: nonEmptyString,
14635
- generation: exports_external.number().int().nonnegative(),
14636
- fromRef: nonEmptyString,
14637
- previousSessionId: nonEmptyString.optional()
14638
- });
14639
- var stateTree = exports_external.strictObject({
14640
- status: exports_external.enum(TREE_STATUSES),
14641
- generation: exports_external.number().int().nonnegative(),
14642
- launchFailures: exports_external.number().int().nonnegative(),
14643
- readyConfirmedAt: exports_external.number().optional(),
14644
- locator: stateTreeLocator.optional(),
14645
- workspaceLost: stateWorkspaceLost.optional()
14646
- });
14647
- var stateGate = exports_external.strictObject({
14648
- artifactId: nonEmptyString,
14649
- latestVersion: exports_external.number().int().positive(),
14650
- approvedVersion: exports_external.number().int().positive().optional()
14651
- });
14652
- var stateRole = exports_external.strictObject({
14653
- role: nonEmptyString,
14654
- issue: nonEmptyString.optional(),
14655
- generation: exports_external.number().int().nonnegative().optional(),
14656
- sessionId: nonEmptyString.optional(),
14657
- readyConfirmedAt: exports_external.number().optional(),
14658
- launchFailures: exports_external.number().int().nonnegative().optional(),
14659
- locator: stateLocator.optional(),
14660
- workspaceLost: stateWorkspaceLost.optional()
14661
- });
14662
- var stateQueuedWorkerIdentity = {
14663
- roleToken: nonEmptyString,
14664
- issue: nonEmptyString,
14665
- role: nonEmptyString
14666
- };
14667
- var stateQueuedWorker = exports_external.union([
14668
- exports_external.strictObject(stateQueuedWorkerIdentity),
14669
- exports_external.strictObject({
14670
- ...stateQueuedWorkerIdentity,
14671
- kind: exports_external.enum(["assignment", "catchup"]),
14672
- queuedAt: nonEmptyString
14673
- })
14674
- ]);
14675
- var LegionDaemonApi = {
14676
- State: {
14677
- response: exports_external.strictObject({
14678
- project: nonEmptyString,
14679
- version: exports_external.number().int(),
14680
- issues: exports_external.record(exports_external.string(), stateIssue),
14681
- trees: exports_external.record(exports_external.string(), stateTree),
14682
- admission: exports_external.strictObject({
14683
- cap: exports_external.number().int().nonnegative(),
14684
- active: exports_external.array(nonEmptyString),
14685
- queue: exports_external.array(nonEmptyString)
14686
- }),
14687
- gates: exports_external.record(exports_external.string(), stateGate),
14688
- controllerLocator: exports_external.union([stateTreeLocator, stateExternalControllerLocator]).optional(),
14689
- roles: exports_external.record(exports_external.string(), stateRole),
14690
- controllerPendingNotices: exports_external.number().int().nonnegative(),
14691
- pendingStatusWrites: exports_external.array(nonEmptyString),
14692
- workerAdmission: exports_external.strictObject({ queue: exports_external.array(stateQueuedWorker) })
14693
- })
14694
- },
14695
- ControllerReady: {
14696
- request: exports_external.strictObject({
14697
- secret: nonEmptyString,
14698
- sessionId: nonEmptyString,
14699
- ompSessionFile: nonEmptyString.optional(),
14700
- pluginVersion: nonEmptyString
14701
- }),
14702
- response: exports_external.object({})
14703
- },
14704
- ControllerSecret: {
14705
- request: exports_external.strictObject({}),
14706
- response: exports_external.object({ secret: nonEmptyString })
14707
- },
14708
- ProcessStarted: {
14709
- request: exports_external.strictObject({
14710
- tree: nonEmptyString,
14711
- generation: exports_external.number().int(),
14712
- rootSessionId: nonEmptyString,
14713
- agentId: nonEmptyString,
14714
- bootToken: nonEmptyString,
14715
- ompSessionFile: nonEmptyString,
14716
- pluginVersion: nonEmptyString
14717
- }),
14718
- response: exports_external.object({
14719
- roleTokens: exports_external.record(exports_external.string(), exports_external.string()),
14720
- controlSubject: nonEmptyString,
14721
- gates: exports_external.object({
14722
- design: exports_external.enum(["root-issues", "off"])
14723
- }).optional(),
14724
- secret: nonEmptyString
14725
- })
14726
- },
14727
- ProcessReady: {
14728
- request: architectCapability.extend({ generation: exports_external.number().int() }),
14729
- response: exports_external.object({})
14730
- },
14731
- ProcessExit: {
14732
- request: architectCapability.extend({ generation: exports_external.number().int() }),
14733
- response: exports_external.object({})
14734
- },
14735
- WaveRelease: {
14736
- request: architectCapability.extend({ issues: exports_external.array(nonEmptyString).optional() }),
14737
- response: exports_external.object({ released: exports_external.array(nonEmptyString) })
14738
- },
14739
- Escalate: {
14740
- request: architectCapability.extend({
14741
- kind: exports_external.enum(["re-file", "capacity", "cross-tree"]),
14742
- context: requiredUnknown
14743
- }),
14744
- response: exports_external.object({})
14745
- },
14746
- ProvisioningCredential: {
14747
- request: architectCapability.extend({ issue: nonEmptyString }),
14748
- response: exports_external.object({ token: nonEmptyString })
14749
- },
14750
- WorkerStarted: {
14751
- request: exports_external.strictObject({
14752
- tree: nonEmptyString,
14753
- issue: nonEmptyString,
14754
- role: legionRole,
14755
- bootToken: nonEmptyString,
14756
- sessionId: nonEmptyString,
14757
- agentId: nonEmptyString,
14758
- ompSessionFile: nonEmptyString,
14759
- pluginVersion: nonEmptyString
14760
- }),
14761
- response: exports_external.object({
14762
- roleToken: nonEmptyString,
14763
- secret: nonEmptyString,
14764
- gitName: nonEmptyString,
14765
- gitEmail: nonEmptyString
14766
- })
14767
- },
14768
- WorkerReady: {
14769
- request: exports_external.strictObject({
14770
- tree: nonEmptyString,
14771
- issue: nonEmptyString,
14772
- role: legionRole,
14773
- sessionId: nonEmptyString,
14774
- generation: exports_external.number().int().nonnegative(),
14775
- secret: nonEmptyString
14776
- }),
14777
- response: exports_external.object({})
14778
- },
14779
- PhaseComplete: {
14780
- request: exports_external.strictObject({
14781
- grantId: nonEmptyString,
14782
- summary: nonEmptyString
14783
- }),
14784
- response: exports_external.object({})
14785
- },
14786
- SpawnWorker: {
14787
- request: architectCapability.extend({
14788
- issue: nonEmptyString,
14789
- role: legionRole,
14790
- task: nonEmptyString,
14791
- requestId: exports_external.uuid()
14792
- }),
14793
- response: exports_external.object({
14794
- status: exports_external.enum(["spawned", "resumed", "queued"]),
14795
- roleToken: nonEmptyString
14796
- })
14797
- },
14798
- WorkerSession: {
14799
- request: exports_external.strictObject({
14800
- sessionId: nonEmptyString,
14801
- recoveryToken: nonEmptyString
14802
- }),
14803
- response: exports_external.object({
14804
- tree: nonEmptyString,
14805
- issue: nonEmptyString,
14806
- role: legionRole,
14807
- secret: nonEmptyString
14808
- })
14809
- },
14810
- IssueStatus: {
14811
- request: controllerIssue.extend({
14812
- status: exports_external.enum(ISSUE_STATUSES),
14813
- tree: nonEmptyString.optional(),
14814
- sessionId: nonEmptyString.optional()
14815
- }),
14816
- response: exports_external.object({})
14817
- },
14818
- GatesRegister: {
14819
- request: architectCapability.extend({
14820
- issue: nonEmptyString,
14821
- artifactId: exports_external.uuid(),
14822
- version: exports_external.number().int().positive()
14823
- }),
14824
- response: exports_external.object({})
14825
- },
14826
- Grant: {
14827
- request: exports_external.union([
14828
- exports_external.strictObject({
14829
- sessionId: nonEmptyString,
14830
- secret: nonEmptyString,
14831
- tree: nonEmptyString,
14832
- issue: nonEmptyString
14833
- }),
14834
- exports_external.strictObject({ sessionId: nonEmptyString, secret: nonEmptyString })
14835
- ]),
14836
- response: exports_external.object({ grantId: nonEmptyString, expiresAt: nonEmptyString })
14837
- },
14838
- GitHubToken: {
14839
- request: exports_external.strictObject({ grantId: nonEmptyString }),
14840
- response: exports_external.object({
14841
- token: nonEmptyString,
14842
- appLogin: exports_external.string().endsWith("[bot]"),
14843
- legionAppLogins: exports_external.object({ implement: appLogin, review: appLogin }).optional()
14844
- })
14845
- },
14846
- GitCredential: {
14847
- request: exports_external.strictObject({ grantId: nonEmptyString })
14848
- }
14849
- };
14850
14411
  // ../contracts/src/repo.ts
14851
14412
  function canonicalRepo(owner, repo) {
14852
14413
  return `${owner.trim().toLowerCase()}/${repo.trim().toLowerCase().replace(/\.git$/, "")}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sjawhar/opencode-legion-envoy",
3
- "version": "4.1.1",
3
+ "version": "5.0.1",
4
4
  "type": "module",
5
5
  "main": "dist/src/server.js",
6
6
  "exports": {
package/skills/AGENTS.md CHANGED
@@ -21,6 +21,6 @@ event intake, process lifecycle, credentials, and role delivery.
21
21
  The owning skill above is where each contract is defined; a role prompt that needs a contract from its own seat points there or restates only its own step. This file lists and does not restate.
22
22
  A Legion prompt (a skill here, a role prompt, or an agent definition in `packages/pi-envoy/agents/`) names a task agent only as `task(agent="<name>")` and a skill it tells the model to load only as `skill://<name>`. Those are the two forms the Go daemon's boot gate and `legion probe-image` resolve through Oh My Pi, refusing by name one it cannot find; a dispatch or a load written any other way goes unchecked. An agent or skill Legion's prompts name is shipped here or in `packages/pi-envoy/agents/`, unless Oh My Pi bundles it.
23
23
  A skill file an agent loads stays under 51,200 bytes, Oh My Pi's spill threshold (a longer one arrives with its middle cut out), and a skill's detail lives in `references/` files linked as `skill://<name>/references/<file>.md`, never by relative path, which a read by path cuts at 300 lines. `packages/pi-envoy/src/skills-guard.test.ts` fails on a file at the threshold, on any `SKILL.md` body of 500 lines or more, on a skill whose frontmatter `name` is not its directory's name, on a `dispatch-first` over its budget, on a `skill://<name>/<path>` link that names a missing file or a missing `#heading`, and on a `legion-worker` reference nothing links.
24
- The text a worker boots with (its role prompt) lives in `packages/pi-envoy/roles/` and is
25
- composed per role in `packages/daemon/src/daemon/processes.ts`; `packages/pi-envoy/roles/roles.test.ts`
26
- holds the structural rules for those parts.
24
+ The text a worker boots with (its role prompt) lives in `packages/daemon/internal/prompts/roles/`,
25
+ embedded in the `legion` binary and composed per role by `packages/daemon/internal/prompts/prompts.go`;
26
+ `packages/daemon/internal/prompts/roles_test.go` holds the structural rules for those parts.
@@ -125,12 +125,10 @@ committed predecessor handoffs in lifecycle order from `$LEGION_WORKSPACE/.legio
125
125
  4. `test.json`
126
126
  5. `review.json`
127
127
 
128
- Read only files that precede the assigned phase. Every handoff is validated when it is read:
129
- `validatePhaseHandoff` (`packages/contracts/src/handoff-schema.ts`) checks the
130
- file, and the ledger (`packages/daemon/src/handoff/ledger.ts`) treats a file that
131
- fails validation as missing.
132
- Undeclared fields pass validation untouched and reach the next worker; a declared field of the
133
- wrong type fails the whole file, so the `legion` tool's `handoff_read` returns null for that phase.
128
+ Read only files that precede the assigned phase. Each was held to its phase's rules when it was
129
+ written (`handoff_write`, in the completion gate below): fields the phase does not declare passed
130
+ untouched and reach the next worker. The `legion` tool's `handoff_read` returns each file as it
131
+ stands in the workspace.
134
132
  Write the phase-specific fields the next phase and the architect need, consistent with what
135
133
  predecessor phases already wrote. The durable copy lives in
136
134
  `$LEGION_WORKSPACE/.legion/<phase>.json`. If a committed handoff conflicts with memory or a prior
@@ -319,7 +317,7 @@ line), the full definition of a proof, what the tester verifies, and the simplif
319
317
  ## Completion gate: handoff write, verification, and persistence
320
318
 
321
319
  The merger writes no handoff and pushes nothing, so this gate does not apply to it
322
- (`packages/pi-envoy/roles/merger.md`).
320
+ (`packages/daemon/internal/prompts/roles/merger.md`).
323
321
 
324
322
  Write the phase-specific handoff: call the `legion` tool with `op: "handoff_write"`, `phase: "<p>"`,
325
323
  and `data`: a JSON object of the phase-specific fields only. It runs `legion handoff write` in
@@ -73,12 +73,12 @@ completion leaves the issue in reviewing until you finish.
73
73
  in READY (an empty output is quoted as `no file changes above the approved head`); then the same
74
74
  with `'~docs/solutions'` appended, which must print nothing. *The READY packet*: the merger
75
75
  always posts `READY #<n> at <current sha> (approved at <approved sha>) for <KEY> (<pr url>)`
76
- (the shape `packages/pi-envoy/roles/merger.md` defines), then the PR body's `Outcome:` line and
77
- its `Not proven / risk:` value — every bullet under that label joined with `; ` on the one
78
- READY line, or `none` — quoted from the `## For the reviewer` block at that same head (or one
79
- line saying the body carries no brief — the packet still publishes), then the `--summary`
80
- output and the PR body's gate facts, as a `dispatch_message` on the issue. When the `Legion
81
- addressing` line names a merge queue, it also publishes the same packet there with
76
+ (the shape `packages/daemon/internal/prompts/roles/merger.md` defines), then the PR body's
77
+ `Outcome:` line and its `Not proven / risk:` value — every bullet under that label joined with
78
+ `; ` on the one READY line, or `none` — quoted from the `## For the reviewer` block at that same
79
+ head (or one line saying the body carries no brief — the packet still publishes), then the
80
+ `--summary` output and the PR body's gate facts, as a `dispatch_message` on the issue. When the
81
+ `Legion addressing` line names a merge queue, it also publishes the same packet there with
82
82
  `envoy_publish`; a 404 means the Dispatch message remains the durable notice and the merger
83
83
  stays idle. The READY packet names both the implementer's and tester's `E2E` lines; a missing
84
84
  one is reported to the architect instead of published. Legion never merges.
@@ -13,7 +13,8 @@ reviewer on every re-review, the merger before READY. Every path it cites is in
13
13
  `Accepted:` — the opener's own follow-up included — leaves the thread open, because resolution
14
14
  considers only the newest comment). The review App can reply on a thread but cannot resolve it:
15
15
  GitHub grants resolving a review thread to the pull request's author, and the implementer opens
16
- every Legion pull request (`packages/daemon/src/daemon/AGENTS.md`, GitHub Apps).
16
+ every Legion pull request (`docs/site/src/content/docs/legion/running-legion.md`, "The two
17
+ GitHub Apps").
17
18
  When `LEGION_GRANT_FILE` or `LEGION_GRANT` is set, use `legion threads resolve --pr <number> --repo <owner>/<repo>`.
18
19
  When neither is set, add `--gh` to that command, which applies the fallback's rule below through
19
20
  your own `gh`; where no `legion` command is installed, use `gh api graphql` with the session's