@sjawhar/pi-legion-envoy 5.28.0 → 5.29.0

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.
package/README.md CHANGED
@@ -71,9 +71,10 @@ at `dist/skills`. The manifest declares `omp.skills: ["dist/skills"]` alongside
71
71
  `<plugin root>/skills` or from directories a manifest's `omp.skills` array names, and this
72
72
  package ships skills at `dist/skills` (not the plugin root itself), so the field is required
73
73
  for every session — including a headless Legion controller — to see the Legion skills at all.
74
- The tarball also ships `agents/`, the task agents Legion's prompts dispatch (`oracle`, the
75
- reviewer's pair `thermonuclear-deep-review` and `thermonuclear-code-quality`, and `deep-worker`,
76
- which writes the implementer's code), which Oh My Pi discovers under any extension package root:
74
+ The tarball also ships `agents/`, the task agents Legion's prompts dispatch (`oracle`; the
75
+ reviewer's pair `thermonuclear-deep-review` and `thermonuclear-code-quality`; `deep-worker`,
76
+ which writes the implementer's code; and the planner's checks, `plan-gap-analyst` before it drafts
77
+ and `plan-reviewer` after), which Oh My Pi discovers under any extension package root:
77
78
  an installed plugin in a pane, the explicit `--extension` root in a Sandbox pod. Each declares its
78
79
  model as a role the operator maps (`docs/kubernetes.md`, "Model roles"). The skills those agents and the
79
80
  role prompts load (the pair's rubrics, the implementer's `ce-simplify-code`) ship in
@@ -0,0 +1,52 @@
1
+ ---
2
+ name: plan-gap-analyst
3
+ description: |
4
+ Pre-planning gap analyst. Read-only. Use before drafting a plan: finds the hidden requirements,
5
+ ambiguities, and missing machine-checkable acceptance criteria an issue leaves unsaid, each with
6
+ what the plan must answer.
7
+ # @oracle is the deployment's `oracle` model role; the Go daemon's boot gate refuses to start unless the operator's settings give
8
+ # this agent a model, through modelRoles.oracle or a task.agentModelOverrides entry for it (docs/kubernetes.md, Operator configuration).
9
+ model: ["@oracle"]
10
+ tools: read, glob, grep, todo
11
+ # The planner waits for the analysis before it drafts; without this, Oh My Pi runs a task agent in
12
+ # the background whenever async jobs are enabled.
13
+ blocking: true
14
+ color: yellow
15
+ ---
16
+
17
+ You read an issue before its plan exists and find what the issue leaves unsaid that would derail
18
+ the plan. You advise the planner; the planner owns the plan.
19
+
20
+ ## What you look for
21
+
22
+ 1. **Hidden requirements.** What the change needs but the issue does not state: a caller, contract,
23
+ migration, configuration, document, or consumer the change reaches; a behaviour the existing
24
+ code promises that the change must keep.
25
+ 2. **Ambiguities.** A sentence with two readings that lead to different work. Name both readings
26
+ and what each would cost.
27
+ 3. **Acceptance a machine cannot check.** A criterion with no command, test, or request whose
28
+ expected output decides it, or a requirement with no criterion at all.
29
+
30
+ Read the code and the repository's own documentation before you call something a gap. A gap the
31
+ issue, the code, or the documentation already answers is not a finding.
32
+
33
+ ## What a finding contains
34
+
35
+ - **The gap**, quoting the issue's words where it has them.
36
+ - **The evidence**: the file and line, or the issue text, that shows it.
37
+ - **What the plan must answer**: the question or decision the plan has to settle and, for an
38
+ acceptance gap, a check a machine could run. Say whether the code settles it or only the
39
+ issue's owner can (a product choice).
40
+
41
+ Report the findings that change the plan, the most work-changing first. Style, edge cases the plan
42
+ can settle in passing, and the design you would have chosen are not findings. When nothing changes
43
+ the plan, say so in one line.
44
+
45
+ ## Constraints
46
+
47
+ - **Read-only: you analyse; you do not implement.** Your file tools are read-only; do not ask for
48
+ others or route edits through other means. Your session also carries the Dispatch and Envoy
49
+ tools: read with them if you need to, but write nothing through them — no issue, comment, ask,
50
+ message, suggestion or document edit, and nothing sent or published.
51
+ - Cite what you read. Anything you did not read is an assumption and is written as one.
52
+ - Do not write the plan.
@@ -0,0 +1,61 @@
1
+ ---
2
+ name: plan-reviewer
3
+ description: |
4
+ Plan executability reviewer. Read-only. Use after drafting a plan: checks that it can be carried
5
+ out as written, approves when in doubt, and names at most three blocking issues, each with its
6
+ evidence.
7
+ # @review is the deployment's `review` model role; the Go daemon's boot gate refuses to start unless the operator's settings give
8
+ # this agent a model, through modelRoles.review or a task.agentModelOverrides entry for it (docs/kubernetes.md, Operator configuration).
9
+ model: ["@review"]
10
+ tools: read, glob, grep, todo
11
+ # The planner waits for each verdict before it revises or proceeds; without this, Oh My Pi runs a
12
+ # task agent in the background whenever async jobs are enabled.
13
+ blocking: true
14
+ color: magenta
15
+ ---
16
+
17
+ You answer one question: can a capable engineer who has only this plan and the repository carry
18
+ it out without getting stuck? You find blockers, not improvements. You advise the planner; the
19
+ planner owns the plan.
20
+
21
+ ## What you check
22
+
23
+ 1. **References.** The files, functions, commands, and skills the plan names exist and hold what
24
+ the plan says. Read them; do not assume.
25
+ 2. **A place to start.** Every task says where the work is and what changes, in an order that can
26
+ be followed.
27
+ 3. **Contradictions.** No two steps contradict each other, and no step contradicts the issue's
28
+ acceptance criteria.
29
+ 4. **Runnable checks.** Every acceptance criterion names the surface and the exact command that
30
+ decides it.
31
+
32
+ You do not judge whether the approach is the best one, style, naming, edge cases the engineer can
33
+ settle during the work, or anything you would simply do differently.
34
+
35
+ ## Verdict
36
+
37
+ Approve when in doubt. A plan that is mostly clear is good enough. Reject only for a blocker: a
38
+ reference that does not exist or does not hold what the plan says (read to confirm), a task with
39
+ nowhere to start, a contradiction, or a criterion nothing can check.
40
+
41
+ A rejection names at most three blocking issues, the most severe first. Each gives:
42
+
43
+ - **The issue**: the exact task, step, or criterion.
44
+ - **The evidence**: the file and line you read, or the plan's own words.
45
+ - **What must change** for it to pass.
46
+
47
+ A revised plan is read again in full and judged fresh: an issue the revision resolved is not
48
+ raised again, and a second round is no place for requests that would not have blocked the first.
49
+
50
+ ## Output
51
+
52
+ The first line is the verdict alone: `approved` or `rejected`. Then one or two sentences on why.
53
+ On `rejected`, the numbered blocking issues. Nothing else.
54
+
55
+ ## Constraints
56
+
57
+ - **Read-only: you review; you do not implement.** Your file tools are read-only; do not ask for
58
+ others or route edits through other means. Your session also carries the Dispatch and Envoy
59
+ tools: read with them if you need to, but write nothing through them — no issue, comment, ask,
60
+ message, suggestion or document edit, and nothing sent or published.
61
+ - Do not rewrite the plan.
package/dist/envoy.js CHANGED
@@ -30477,6 +30477,8 @@ var EnvelopeSchema = exports_external.object({
30477
30477
  // ../contracts/src/handoff-schema.ts
30478
30478
  var HANDOFF_SCHEMA_VERSION = 1;
30479
30479
  var HANDOFF_PHASES = ["architect", "plan", "implement", "test", "review"];
30480
+ var PLAN_REVIEW_MAX_ROUNDS = 3;
30481
+ var PLAN_REVIEW_VERDICTS = ["approved", "rejected", "failed"];
30480
30482
  var isoTimestamp = exports_external.string().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/);
30481
30483
  var handoffPhase = exports_external.enum(HANDOFF_PHASES);
30482
30484
  var nonEmpty = exports_external.string().trim().min(1);
@@ -30513,6 +30515,16 @@ var requiredSkillsSchema = exports_external.object({
30513
30515
  test: exports_external.array(exports_external.string()).optional(),
30514
30516
  review: exports_external.array(exports_external.string()).optional()
30515
30517
  }).passthrough().optional();
30518
+ var gapAnalysisSchema = exports_external.object({
30519
+ findings: exports_external.array(exports_external.object({ finding: exports_external.string(), answer: exports_external.string() }).passthrough()).optional(),
30520
+ error: exports_external.string().optional()
30521
+ }).passthrough().optional();
30522
+ var planReviewSchema = exports_external.object({
30523
+ verdict: exports_external.enum(PLAN_REVIEW_VERDICTS),
30524
+ rounds: exports_external.number(),
30525
+ remainingIssues: exports_external.array(exports_external.object({ issue: exports_external.string(), evidence: exports_external.string() }).passthrough()).optional(),
30526
+ error: exports_external.string().optional()
30527
+ }).passthrough().optional();
30516
30528
  var planSchema = baseHandoffSchema.extend({
30517
30529
  phase: exports_external.literal("plan"),
30518
30530
  taskCount: exports_external.number().optional(),
@@ -30520,7 +30532,9 @@ var planSchema = baseHandoffSchema.extend({
30520
30532
  routingHints: routingHintsSchema,
30521
30533
  concerns: exports_external.array(exports_external.string()).optional(),
30522
30534
  workflowRecommendation: exports_external.string().optional(),
30523
- requiredSkills: requiredSkillsSchema
30535
+ requiredSkills: requiredSkillsSchema,
30536
+ gapAnalysis: gapAnalysisSchema,
30537
+ planReview: planReviewSchema
30524
30538
  });
30525
30539
  var implementSchema = baseHandoffSchema.extend({
30526
30540
  phase: exports_external.literal("implement"),
@@ -30560,13 +30574,60 @@ var reviewSchema = baseHandoffSchema.extend({
30560
30574
  verdict: exports_external.enum(["approved", "changes_requested"]).optional(),
30561
30575
  keyFindings: exports_external.array(exports_external.object({ severity: exports_external.string(), file: exports_external.string(), description: exports_external.string() }).passthrough()).optional()
30562
30576
  });
30563
- var nonEmptySkillList = exports_external.array(exports_external.string().trim().min(1)).min(1);
30577
+ var nonEmptySkillList = exports_external.array(nonEmpty).min(1);
30578
+ var recorded = (shape, whatToRecord) => exports_external.object(shape, {
30579
+ error: (issue2) => issue2.input === undefined ? `missing \u2014 record ${whatToRecord}` : undefined
30580
+ }).passthrough();
30581
+ var gapAnalysisWriteSchema = recorded({
30582
+ findings: exports_external.array(exports_external.object({ finding: nonEmpty, answer: nonEmpty }).passthrough()).optional(),
30583
+ error: nonEmpty.optional()
30584
+ }, "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), {
30585
+ message: "record exactly one of `findings` or the failed call's `error`"
30586
+ });
30587
+ var planReviewWriteSchema = recorded({
30588
+ verdict: exports_external.enum(PLAN_REVIEW_VERDICTS),
30589
+ rounds: exports_external.number().int().min(1).max(PLAN_REVIEW_MAX_ROUNDS),
30590
+ remainingIssues: exports_external.array(exports_external.object({ issue: nonEmpty, evidence: nonEmpty }).passthrough()).optional(),
30591
+ error: nonEmpty.optional()
30592
+ }, "the plan review's `verdict` and `rounds`, with `remainingIssues` when it was rejected or `error` when a review's call failed").superRefine((review, ctx) => {
30593
+ const remaining = review.remainingIssues?.length ?? 0;
30594
+ if (review.verdict === "rejected" && remaining === 0) {
30595
+ ctx.addIssue({
30596
+ code: "custom",
30597
+ path: ["remainingIssues"],
30598
+ message: "a rejected review records the blocking issues its last round named"
30599
+ });
30600
+ }
30601
+ if (review.verdict === "rejected" && review.rounds < PLAN_REVIEW_MAX_ROUNDS) {
30602
+ ctx.addIssue({
30603
+ code: "custom",
30604
+ path: ["rounds"],
30605
+ message: `a review still rejecting after ${review.rounds} of ${PLAN_REVIEW_MAX_ROUNDS} rounds is revised and reviewed again, not recorded`
30606
+ });
30607
+ }
30608
+ if (review.verdict === "approved" && remaining > 0) {
30609
+ ctx.addIssue({
30610
+ code: "custom",
30611
+ path: ["remainingIssues"],
30612
+ message: "an approved review leaves no blocking issue standing"
30613
+ });
30614
+ }
30615
+ if (review.verdict === "failed" !== (review.error !== undefined)) {
30616
+ ctx.addIssue({
30617
+ code: "custom",
30618
+ path: ["error"],
30619
+ message: "a failed review records its call's error, and only a failed review does"
30620
+ });
30621
+ }
30622
+ });
30564
30623
  var planWriteSchema = planSchema.extend({
30565
30624
  requiredSkills: exports_external.object({
30566
30625
  implement: nonEmptySkillList,
30567
30626
  test: nonEmptySkillList,
30568
30627
  review: nonEmptySkillList
30569
- }).passthrough()
30628
+ }).passthrough(),
30629
+ gapAnalysis: gapAnalysisWriteSchema,
30630
+ planReview: planReviewWriteSchema
30570
30631
  });
30571
30632
  var phaseHandoffSchema = exports_external.discriminatedUnion("phase", [
30572
30633
  architectSchema,
@@ -35400,9 +35461,9 @@ function resolveEnvoySession(sessionFile) {
35400
35461
  const parent = `${directory}.jsonl`;
35401
35462
  if (parent === candidate)
35402
35463
  break;
35403
- const recorded = sessions.get(parent);
35404
- if (recorded !== undefined)
35405
- return recorded;
35464
+ const recorded2 = sessions.get(parent);
35465
+ if (recorded2 !== undefined)
35466
+ return recorded2;
35406
35467
  if (path2.dirname(directory) === directory)
35407
35468
  break;
35408
35469
  candidate = parent;
package/dist/legion.js CHANGED
@@ -30646,6 +30646,8 @@ var EnvelopeSchema = exports_external.object({
30646
30646
  // ../contracts/src/handoff-schema.ts
30647
30647
  var HANDOFF_SCHEMA_VERSION = 1;
30648
30648
  var HANDOFF_PHASES = ["architect", "plan", "implement", "test", "review"];
30649
+ var PLAN_REVIEW_MAX_ROUNDS = 3;
30650
+ var PLAN_REVIEW_VERDICTS = ["approved", "rejected", "failed"];
30649
30651
  var isoTimestamp = exports_external.string().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/);
30650
30652
  var handoffPhase = exports_external.enum(HANDOFF_PHASES);
30651
30653
  var nonEmpty = exports_external.string().trim().min(1);
@@ -30682,6 +30684,16 @@ var requiredSkillsSchema = exports_external.object({
30682
30684
  test: exports_external.array(exports_external.string()).optional(),
30683
30685
  review: exports_external.array(exports_external.string()).optional()
30684
30686
  }).passthrough().optional();
30687
+ var gapAnalysisSchema = exports_external.object({
30688
+ findings: exports_external.array(exports_external.object({ finding: exports_external.string(), answer: exports_external.string() }).passthrough()).optional(),
30689
+ error: exports_external.string().optional()
30690
+ }).passthrough().optional();
30691
+ var planReviewSchema = exports_external.object({
30692
+ verdict: exports_external.enum(PLAN_REVIEW_VERDICTS),
30693
+ rounds: exports_external.number(),
30694
+ remainingIssues: exports_external.array(exports_external.object({ issue: exports_external.string(), evidence: exports_external.string() }).passthrough()).optional(),
30695
+ error: exports_external.string().optional()
30696
+ }).passthrough().optional();
30685
30697
  var planSchema = baseHandoffSchema.extend({
30686
30698
  phase: exports_external.literal("plan"),
30687
30699
  taskCount: exports_external.number().optional(),
@@ -30689,7 +30701,9 @@ var planSchema = baseHandoffSchema.extend({
30689
30701
  routingHints: routingHintsSchema,
30690
30702
  concerns: exports_external.array(exports_external.string()).optional(),
30691
30703
  workflowRecommendation: exports_external.string().optional(),
30692
- requiredSkills: requiredSkillsSchema
30704
+ requiredSkills: requiredSkillsSchema,
30705
+ gapAnalysis: gapAnalysisSchema,
30706
+ planReview: planReviewSchema
30693
30707
  });
30694
30708
  var implementSchema = baseHandoffSchema.extend({
30695
30709
  phase: exports_external.literal("implement"),
@@ -30729,13 +30743,60 @@ var reviewSchema = baseHandoffSchema.extend({
30729
30743
  verdict: exports_external.enum(["approved", "changes_requested"]).optional(),
30730
30744
  keyFindings: exports_external.array(exports_external.object({ severity: exports_external.string(), file: exports_external.string(), description: exports_external.string() }).passthrough()).optional()
30731
30745
  });
30732
- var nonEmptySkillList = exports_external.array(exports_external.string().trim().min(1)).min(1);
30746
+ var nonEmptySkillList = exports_external.array(nonEmpty).min(1);
30747
+ var recorded = (shape, whatToRecord) => exports_external.object(shape, {
30748
+ error: (issue2) => issue2.input === undefined ? `missing \u2014 record ${whatToRecord}` : undefined
30749
+ }).passthrough();
30750
+ var gapAnalysisWriteSchema = recorded({
30751
+ findings: exports_external.array(exports_external.object({ finding: nonEmpty, answer: nonEmpty }).passthrough()).optional(),
30752
+ error: nonEmpty.optional()
30753
+ }, "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), {
30754
+ message: "record exactly one of `findings` or the failed call's `error`"
30755
+ });
30756
+ var planReviewWriteSchema = recorded({
30757
+ verdict: exports_external.enum(PLAN_REVIEW_VERDICTS),
30758
+ rounds: exports_external.number().int().min(1).max(PLAN_REVIEW_MAX_ROUNDS),
30759
+ remainingIssues: exports_external.array(exports_external.object({ issue: nonEmpty, evidence: nonEmpty }).passthrough()).optional(),
30760
+ error: nonEmpty.optional()
30761
+ }, "the plan review's `verdict` and `rounds`, with `remainingIssues` when it was rejected or `error` when a review's call failed").superRefine((review, ctx) => {
30762
+ const remaining = review.remainingIssues?.length ?? 0;
30763
+ if (review.verdict === "rejected" && remaining === 0) {
30764
+ ctx.addIssue({
30765
+ code: "custom",
30766
+ path: ["remainingIssues"],
30767
+ message: "a rejected review records the blocking issues its last round named"
30768
+ });
30769
+ }
30770
+ if (review.verdict === "rejected" && review.rounds < PLAN_REVIEW_MAX_ROUNDS) {
30771
+ ctx.addIssue({
30772
+ code: "custom",
30773
+ path: ["rounds"],
30774
+ message: `a review still rejecting after ${review.rounds} of ${PLAN_REVIEW_MAX_ROUNDS} rounds is revised and reviewed again, not recorded`
30775
+ });
30776
+ }
30777
+ if (review.verdict === "approved" && remaining > 0) {
30778
+ ctx.addIssue({
30779
+ code: "custom",
30780
+ path: ["remainingIssues"],
30781
+ message: "an approved review leaves no blocking issue standing"
30782
+ });
30783
+ }
30784
+ if (review.verdict === "failed" !== (review.error !== undefined)) {
30785
+ ctx.addIssue({
30786
+ code: "custom",
30787
+ path: ["error"],
30788
+ message: "a failed review records its call's error, and only a failed review does"
30789
+ });
30790
+ }
30791
+ });
30733
30792
  var planWriteSchema = planSchema.extend({
30734
30793
  requiredSkills: exports_external.object({
30735
30794
  implement: nonEmptySkillList,
30736
30795
  test: nonEmptySkillList,
30737
30796
  review: nonEmptySkillList
30738
- }).passthrough()
30797
+ }).passthrough(),
30798
+ gapAnalysis: gapAnalysisWriteSchema,
30799
+ planReview: planReviewWriteSchema
30739
30800
  });
30740
30801
  var phaseHandoffSchema = exports_external.discriminatedUnion("phase", [
30741
30802
  architectSchema,
@@ -33611,7 +33672,7 @@ import { logger } from "@oh-my-pi/pi-utils";
33611
33672
  // package.json
33612
33673
  var package_default = {
33613
33674
  name: "@sjawhar/pi-legion-envoy",
33614
- version: "5.28.0",
33675
+ version: "5.29.0",
33615
33676
  type: "module",
33616
33677
  omp: {
33617
33678
  extensions: [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sjawhar/pi-legion-envoy",
3
- "version": "5.28.0",
3
+ "version": "5.29.0",
4
4
  "type": "module",
5
5
  "omp": {
6
6
  "extensions": [