@webpresso/agent-kit 3.1.26 → 3.1.30

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.
Files changed (150) hide show
  1. package/catalog/AGENTS.md.tpl +7 -11
  2. package/catalog/agent/commands/verify.md +2 -2
  3. package/catalog/agent/rules/changeset-release.md +4 -2
  4. package/catalog/agent/rules/ci-cost-local-first.md +139 -0
  5. package/catalog/agent/rules/ci-test-perf.md +5 -1
  6. package/catalog/agent/rules/cmd-execution.md +9 -0
  7. package/catalog/agent/rules/pre-implementation.md +2 -1
  8. package/catalog/agent/rules/test-scan-perf.md +80 -0
  9. package/catalog/agent/rules/workflow-skills-routing.md +9 -4
  10. package/catalog/agent/skills/autopilot/SKILL.md +48 -11
  11. package/catalog/agent/skills/claude/SKILL.md +7 -0
  12. package/catalog/agent/skills/codex/SKILL.md +7 -0
  13. package/catalog/agent/skills/fix/SKILL.md +36 -8
  14. package/catalog/agent/skills/grok/SKILL.md +7 -0
  15. package/catalog/agent/skills/investigate/SKILL.md +12 -1
  16. package/catalog/agent/skills/pll/SKILL.md +53 -48
  17. package/catalog/agent/skills/team/SKILL.md +26 -9
  18. package/catalog/agent/skills/ultragoal/SKILL.md +147 -19
  19. package/catalog/agent/skills/verify/SKILL.md +89 -51
  20. package/dist/esm/audit/blueprint-lifecycle-sql.d.ts +8 -0
  21. package/dist/esm/audit/blueprint-lifecycle-sql.js +25 -1
  22. package/dist/esm/audit/blueprint-pr-coverage.js +68 -7
  23. package/dist/esm/audit/registry.d.ts +4 -4
  24. package/dist/esm/audit/registry.js +3 -0
  25. package/dist/esm/audit/roadmap-links.js +6 -0
  26. package/dist/esm/audit/test-scan-perf.d.ts +18 -0
  27. package/dist/esm/audit/test-scan-perf.js +275 -0
  28. package/dist/esm/blueprint/lifecycle/engine.d.ts +9 -0
  29. package/dist/esm/blueprint/lifecycle/engine.js +53 -6
  30. package/dist/esm/blueprint/lifecycle/review-provenance.d.ts +6 -0
  31. package/dist/esm/blueprint/lifecycle/review-provenance.js +93 -12
  32. package/dist/esm/blueprint/markdown/helpers.js +6 -2
  33. package/dist/esm/ci/act-replay.js +110 -7
  34. package/dist/esm/ci/act-runner.d.ts +4 -0
  35. package/dist/esm/ci/act-runner.js +85 -26
  36. package/dist/esm/ci/act-worktree-git.d.ts +11 -0
  37. package/dist/esm/ci/act-worktree-git.js +219 -0
  38. package/dist/esm/ci/native-session-memory-cache.d.ts +9 -1
  39. package/dist/esm/ci/native-session-memory-cache.js +9 -1
  40. package/dist/esm/ci/vitest-ci-shards.d.ts +6 -4
  41. package/dist/esm/ci/vitest-ci-shards.js +6 -4
  42. package/dist/esm/cli/auto-update/guard-tombstone.d.ts +25 -0
  43. package/dist/esm/cli/auto-update/guard-tombstone.js +67 -0
  44. package/dist/esm/cli/auto-update/installer.d.ts +4 -17
  45. package/dist/esm/cli/auto-update/installer.js +6 -57
  46. package/dist/esm/cli/auto-update/self-invocation.d.ts +37 -0
  47. package/dist/esm/cli/auto-update/self-invocation.js +62 -0
  48. package/dist/esm/cli/commands/audit.js +4 -0
  49. package/dist/esm/cli/commands/blueprint/mutations.js +35 -18
  50. package/dist/esm/cli/commands/blueprint/router.js +27 -2
  51. package/dist/esm/cli/commands/ci.d.ts +8 -1
  52. package/dist/esm/cli/commands/ci.js +80 -21
  53. package/dist/esm/cli/commands/dash/index.js +3 -3
  54. package/dist/esm/cli/commands/dash/keymap.js +1 -0
  55. package/dist/esm/cli/commands/dash/launch-preferences.d.ts +15 -0
  56. package/dist/esm/cli/commands/dash/launch-preferences.js +46 -17
  57. package/dist/esm/cli/commands/dash/plan-once-attention.d.ts +32 -0
  58. package/dist/esm/cli/commands/dash/plan-once-attention.js +50 -0
  59. package/dist/esm/cli/commands/dash/plan-once-label.d.ts +7 -0
  60. package/dist/esm/cli/commands/dash/plan-once-label.js +5 -0
  61. package/dist/esm/cli/commands/dash/plan-once-membership.d.ts +67 -0
  62. package/dist/esm/cli/commands/dash/plan-once-membership.js +151 -0
  63. package/dist/esm/cli/commands/dash/plan-once-merge.d.ts +143 -0
  64. package/dist/esm/cli/commands/dash/plan-once-merge.js +238 -0
  65. package/dist/esm/cli/commands/dash/plan-once-schema.d.ts +73 -0
  66. package/dist/esm/cli/commands/dash/plan-once-schema.js +119 -0
  67. package/dist/esm/cli/commands/dash/plan-once.d.ts +142 -0
  68. package/dist/esm/cli/commands/dash/plan-once.js +261 -0
  69. package/dist/esm/cli/commands/dash/tui/attention-hud.d.ts +9 -0
  70. package/dist/esm/cli/commands/dash/tui/attention-hud.js +9 -0
  71. package/dist/esm/cli/commands/dash/tui/chat-bridge.d.ts +41 -0
  72. package/dist/esm/cli/commands/dash/tui/chat-bridge.js +73 -0
  73. package/dist/esm/cli/commands/dash/tui/chat-mode.d.ts +47 -0
  74. package/dist/esm/cli/commands/dash/tui/chat-mode.js +98 -0
  75. package/dist/esm/cli/commands/dash/tui/frame.d.ts +3 -0
  76. package/dist/esm/cli/commands/dash/tui/frame.js +3 -1
  77. package/dist/esm/cli/commands/dash/tui/ipc-protocol.d.ts +87 -0
  78. package/dist/esm/cli/commands/dash/tui/ipc-protocol.js +128 -0
  79. package/dist/esm/cli/commands/dash/tui/native-host.d.ts +34 -0
  80. package/dist/esm/cli/commands/dash/tui/native-host.js +256 -0
  81. package/dist/esm/cli/commands/dash/tui/native-path.d.ts +11 -0
  82. package/dist/esm/cli/commands/dash/tui/native-path.js +41 -0
  83. package/dist/esm/cli/commands/dash/tui/palette.d.ts +1 -1
  84. package/dist/esm/cli/commands/dash/tui/palette.js +14 -0
  85. package/dist/esm/cli/commands/dash/tui/runtime.js +311 -4
  86. package/dist/esm/cli/commands/dash/tui/state.d.ts +41 -0
  87. package/dist/esm/cli/commands/dash/tui/state.js +257 -4
  88. package/dist/esm/cli/commands/format.js +0 -2
  89. package/dist/esm/cli/commands/init/config.d.ts +0 -1
  90. package/dist/esm/cli/commands/init/config.js +1 -4
  91. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/emitters/claude.d.ts +5 -6
  92. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/emitters/claude.js +6 -13
  93. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/ir.d.ts +0 -2
  94. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/ir.js +0 -2
  95. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/merge.d.ts +6 -3
  96. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/merge.js +39 -26
  97. package/dist/esm/cli/commands/init/scaffolders/agent-kit-global/index.js +1 -1
  98. package/dist/esm/cli/commands/init/scaffolders/codex-mcp/index.js +61 -23
  99. package/dist/esm/cli/commands/logs.d.ts +15 -0
  100. package/dist/esm/cli/commands/logs.js +192 -3
  101. package/dist/esm/cli/commands/package-manager.js +2 -1
  102. package/dist/esm/cli/commands/quality-log-store.d.ts +1 -0
  103. package/dist/esm/cli/commands/quality-log-store.js +187 -51
  104. package/dist/esm/cli/commands/review.js +5 -8
  105. package/dist/esm/cli/commands/secrets.d.ts +1 -0
  106. package/dist/esm/cli/commands/secrets.js +4 -1
  107. package/dist/esm/cli/commands/self-install-guard.d.ts +14 -32
  108. package/dist/esm/cli/commands/self-install-guard.js +17 -63
  109. package/dist/esm/cli/direct-provider-launch.js +25 -1
  110. package/dist/esm/hooks/doctor.d.ts +15 -0
  111. package/dist/esm/hooks/doctor.js +56 -0
  112. package/dist/esm/hooks/guard-switch/index.js +8 -4
  113. package/dist/esm/hooks/post-tool/lint-after-edit.js +6 -4
  114. package/dist/esm/hooks/precompact/index.js +5 -3
  115. package/dist/esm/hooks/pretool-guard/validators/forbidden-commands.js +4 -4
  116. package/dist/esm/hooks/pretool-guard/validators/mcp-redirect.d.ts +0 -1
  117. package/dist/esm/hooks/pretool-guard/validators/mcp-redirect.js +8 -5
  118. package/dist/esm/hooks/pretool-guard/validators/worktree-discipline.d.ts +9 -7
  119. package/dist/esm/hooks/pretool-guard/validators/worktree-discipline.js +126 -14
  120. package/dist/esm/hooks/sessionstart/index.d.ts +10 -0
  121. package/dist/esm/hooks/sessionstart/index.js +61 -4
  122. package/dist/esm/hooks/shared/types.d.ts +11 -0
  123. package/dist/esm/hooks/shared/types.js +21 -0
  124. package/dist/esm/mcp/tools/_shared/audit-kinds.d.ts +1 -1
  125. package/dist/esm/mcp/tools/audit.d.ts +2 -1
  126. package/dist/esm/mcp/tools/audit.js +10 -0
  127. package/dist/esm/mcp/tools/audits.d.ts +1 -0
  128. package/dist/esm/mcp/tools/ci-act.js +9 -11
  129. package/dist/esm/paths/state-root.js +9 -0
  130. package/dist/esm/review/authority.js +17 -2
  131. package/dist/esm/review/availability.js +18 -5
  132. package/dist/esm/review/subject.d.ts +15 -0
  133. package/dist/esm/review/subject.js +105 -10
  134. package/dist/esm/runtime/executor.d.ts +1 -0
  135. package/dist/esm/runtime/executor.js +2 -1
  136. package/dist/esm/secret-gate/runner.d.ts +1 -0
  137. package/dist/esm/secret-gate/runner.js +4 -1
  138. package/dist/esm/session-memory/current-session.js +2 -0
  139. package/dist/esm/session-memory/native-runtime.js +5 -3
  140. package/dist/esm/session-memory/native-warm-markers.d.ts +18 -0
  141. package/dist/esm/session-memory/native-warm-markers.js +22 -0
  142. package/dist/esm/session-memory/sync/types.d.ts +4 -0
  143. package/dist/esm/session-memory/types.d.ts +3 -1
  144. package/dist/esm/session-memory/types.js +2 -0
  145. package/dist/esm/ultragoal/runtime.js +44 -2
  146. package/dist/esm/worktrees/current.d.ts +1 -0
  147. package/dist/esm/worktrees/current.js +1 -0
  148. package/dist/esm/worktrees/project-agent-surfaces.d.ts +1 -0
  149. package/dist/esm/worktrees/project-agent-surfaces.js +2 -1
  150. package/package.json +17 -14
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Durable plan-once membership (expected host set for a planId).
3
+ *
4
+ * Labels alone are not N-of-N SSOT: a host that never creates a run disappears
5
+ * from registry-only views. Membership is written on confirm (not cancel) and
6
+ * is a plan-record sidecar — not a second run-registry.
7
+ */
8
+ import { copyFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
9
+ import { randomUUID } from "node:crypto";
10
+ import { dirname } from "node:path";
11
+ import { z } from "zod";
12
+ import { planOnceRunLabel } from "#cli/commands/dash/plan-once-label.js";
13
+ import { planOnceHostSchema, } from "#cli/commands/dash/plan-once-schema.js";
14
+ import { getSurfacePathAsync, withLock } from "#paths/state-root.js";
15
+ import { writeJsonFileAtomic } from "#shared-utils/write-json-file.js";
16
+ export const PLAN_MEMBERSHIP_SCHEMA_VERSION = 1;
17
+ const MEMBERSHIP_SURFACE = "dash/plan-once-memberships.json";
18
+ export const planMembershipHostSchema = z
19
+ .object({
20
+ host: planOnceHostSchema,
21
+ workdir: z.string().min(1),
22
+ /** Expected run label: plan-once:<planId>:<label|host> */
23
+ label: z.string().min(1),
24
+ })
25
+ .strict();
26
+ export const planMembershipSchema = z
27
+ .object({
28
+ schemaVersion: z.literal(PLAN_MEMBERSHIP_SCHEMA_VERSION),
29
+ planId: z.string().uuid(),
30
+ hosts: z.array(planMembershipHostSchema).min(1),
31
+ confirmedAt: z.string().datetime(),
32
+ })
33
+ .strict();
34
+ export const planMembershipStoreSchema = z
35
+ .object({
36
+ version: z.literal(1),
37
+ plans: z.record(z.string(), planMembershipSchema),
38
+ })
39
+ .strict();
40
+ function emptyStore() {
41
+ return { version: 1, plans: {} };
42
+ }
43
+ /** Pure: build immutable membership from a confirmed multi-host plan. */
44
+ export function buildPlanMembership(plan, confirmedAt = new Date().toISOString()) {
45
+ const hosts = plan.hosts.map((entry) => ({
46
+ host: entry.host,
47
+ workdir: entry.workdir,
48
+ label: planOnceRunLabel(plan.planId, entry),
49
+ }));
50
+ return planMembershipSchema.parse({
51
+ schemaVersion: PLAN_MEMBERSHIP_SCHEMA_VERSION,
52
+ planId: plan.planId,
53
+ hosts,
54
+ confirmedAt,
55
+ });
56
+ }
57
+ async function membershipPath(cwd) {
58
+ return getSurfacePathAsync(MEMBERSHIP_SURFACE, "repo", cwd);
59
+ }
60
+ function corruptCopyPath(path) {
61
+ return `${path}.corrupt-${new Date().toISOString().replaceAll(":", "-")}-${randomUUID()}`;
62
+ }
63
+ function preserveStore(path) {
64
+ if (!existsSync(path))
65
+ return undefined;
66
+ const preservedPath = corruptCopyPath(path);
67
+ try {
68
+ copyFileSync(path, preservedPath);
69
+ return preservedPath;
70
+ }
71
+ catch {
72
+ return undefined;
73
+ }
74
+ }
75
+ /**
76
+ * Read membership store. Missing file → empty (ok).
77
+ * Corrupt/unreadable file → ok:false with quarantine path; never treat as empty
78
+ * for write merge (would wipe sibling plan memberships).
79
+ */
80
+ function readStoreFromPath(path) {
81
+ if (!existsSync(path))
82
+ return { ok: true, store: emptyStore() };
83
+ try {
84
+ const raw = JSON.parse(readFileSync(path, "utf8"));
85
+ const parsed = planMembershipStoreSchema.safeParse(raw);
86
+ if (parsed.success)
87
+ return { ok: true, store: parsed.data };
88
+ const preservedPath = preserveStore(path);
89
+ return {
90
+ ok: false,
91
+ reason: "plan membership store failed schema validation",
92
+ ...(preservedPath ? { preservedPath } : {}),
93
+ };
94
+ }
95
+ catch (error) {
96
+ const preservedPath = preserveStore(path);
97
+ return {
98
+ ok: false,
99
+ reason: error instanceof Error ? error.message : "plan membership store unreadable",
100
+ ...(preservedPath ? { preservedPath } : {}),
101
+ };
102
+ }
103
+ }
104
+ function storeForRead(path) {
105
+ const read = readStoreFromPath(path);
106
+ // Projection reads fail closed to empty (no memberships) without wiping disk.
107
+ return read.ok ? read.store : emptyStore();
108
+ }
109
+ /** Persist membership for a planId (idempotent overwrite for same planId). */
110
+ export async function writePlanMembership(membership, options = {}) {
111
+ const validated = planMembershipSchema.parse(membership);
112
+ const path = await membershipPath(options.cwd);
113
+ return withLock("repo", () => {
114
+ const read = readStoreFromPath(path);
115
+ if (!read.ok) {
116
+ const preserved = read.preservedPath ? ` (preserved ${read.preservedPath})` : "";
117
+ throw new Error(`Refusing to write plan membership: store corrupt — ${read.reason}${preserved}`);
118
+ }
119
+ const next = {
120
+ version: 1,
121
+ plans: { ...read.store.plans, [validated.planId]: validated },
122
+ };
123
+ mkdirSync(dirname(path), { recursive: true });
124
+ writeJsonFileAtomic(path, planMembershipStoreSchema.parse(next));
125
+ return validated;
126
+ }, options.cwd);
127
+ }
128
+ /** Read membership for planId, or null if absent. */
129
+ export async function readPlanMembership(planId, options = {}) {
130
+ const path = await membershipPath(options.cwd);
131
+ return withLock("repo", () => {
132
+ const store = storeForRead(path);
133
+ return store.plans[planId] ?? null;
134
+ }, options.cwd);
135
+ }
136
+ /** List all durable plan memberships (operator / merge projection scan). */
137
+ export async function listPlanMemberships(options = {}) {
138
+ const path = await membershipPath(options.cwd);
139
+ return withLock("repo", () => {
140
+ const store = storeForRead(path);
141
+ return Object.values(store.plans);
142
+ }, options.cwd);
143
+ }
144
+ /** Test helper: clear membership store file content via rewrite empty. */
145
+ export async function clearPlanMembershipStoreForTests(options) {
146
+ const path = await membershipPath(options.cwd);
147
+ await withLock("repo", () => {
148
+ mkdirSync(dirname(path), { recursive: true });
149
+ writeJsonFileAtomic(path, emptyStore());
150
+ }, options.cwd);
151
+ }
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Merge-set projection: membership (expected hosts) + registry runs → rollup.
3
+ * Pure given membership + run observations — no second run store.
4
+ *
5
+ * Operator surface: summary + per-host matrix only. Git/PR auto-merge is out of
6
+ * scope (no silent git merge of host work).
7
+ */
8
+ import { z } from "zod";
9
+ import { type FanOutAttentionProjection } from "#cli/commands/dash/plan-once-attention.js";
10
+ import { type PlanMembership } from "#cli/commands/dash/plan-once-membership.js";
11
+ export declare const MERGE_ROLLUPS: readonly ["all-ok", "partial", "all-failed", "incomplete"];
12
+ export type MergeRollup = (typeof MERGE_ROLLUPS)[number];
13
+ /** Rollups that should occupy the attention strip (all-ok does not). */
14
+ export declare const MERGE_ATTENTION_ROLLUPS: readonly ["incomplete", "partial", "all-failed"];
15
+ export declare const mergeRollupSchema: z.ZodEnum<{
16
+ "all-failed": "all-failed";
17
+ "all-ok": "all-ok";
18
+ incomplete: "incomplete";
19
+ partial: "partial";
20
+ }>;
21
+ export declare const mergeHostStatusSchema: z.ZodEnum<{
22
+ failed: "failed";
23
+ missing: "missing";
24
+ ok: "ok";
25
+ unknown: "unknown";
26
+ }>;
27
+ export declare const mergeHostObservationSchema: z.ZodObject<{
28
+ host: z.ZodString;
29
+ workdir: z.ZodString;
30
+ expectedLabel: z.ZodString;
31
+ runId: z.ZodNullable<z.ZodString>;
32
+ state: z.ZodNullable<z.ZodString>;
33
+ error: z.ZodNullable<z.ZodString>;
34
+ status: z.ZodEnum<{
35
+ failed: "failed";
36
+ missing: "missing";
37
+ ok: "ok";
38
+ unknown: "unknown";
39
+ }>;
40
+ }, z.core.$strict>;
41
+ export type MergeHostObservation = z.infer<typeof mergeHostObservationSchema>;
42
+ export type MergeSetProjection = {
43
+ readonly planId: string;
44
+ readonly expectedCount: number;
45
+ readonly observedCount: number;
46
+ readonly missingCount: number;
47
+ readonly failedCount: number;
48
+ readonly okCount: number;
49
+ readonly rollup: MergeRollup;
50
+ readonly hosts: readonly MergeHostObservation[];
51
+ readonly attention: FanOutAttentionProjection;
52
+ };
53
+ /** Serializable subset of merge projection (attention omitted). */
54
+ export declare const mergeSetCoreSchema: z.ZodObject<{
55
+ planId: z.ZodString;
56
+ expectedCount: z.ZodNumber;
57
+ observedCount: z.ZodNumber;
58
+ missingCount: z.ZodNumber;
59
+ failedCount: z.ZodNumber;
60
+ okCount: z.ZodNumber;
61
+ rollup: z.ZodEnum<{
62
+ "all-failed": "all-failed";
63
+ "all-ok": "all-ok";
64
+ incomplete: "incomplete";
65
+ partial: "partial";
66
+ }>;
67
+ hosts: z.ZodArray<z.ZodObject<{
68
+ host: z.ZodString;
69
+ workdir: z.ZodString;
70
+ expectedLabel: z.ZodString;
71
+ runId: z.ZodNullable<z.ZodString>;
72
+ state: z.ZodNullable<z.ZodString>;
73
+ error: z.ZodNullable<z.ZodString>;
74
+ status: z.ZodEnum<{
75
+ failed: "failed";
76
+ missing: "missing";
77
+ ok: "ok";
78
+ unknown: "unknown";
79
+ }>;
80
+ }, z.core.$strict>>;
81
+ }, z.core.$strict>;
82
+ export type MergeRunRow = {
83
+ readonly id: string;
84
+ readonly provider: string;
85
+ readonly state: string;
86
+ readonly error?: string | null;
87
+ readonly label?: string | null;
88
+ /** ISO timestamp when present — used to prefer newest re-apply label match. */
89
+ readonly updatedAt?: string | null;
90
+ };
91
+ /**
92
+ * Project merge set from durable membership + registry-visible runs.
93
+ * Missing expected host → incomplete (never all-ok).
94
+ */
95
+ export declare function projectMergeSet(input: {
96
+ readonly membership: PlanMembership;
97
+ readonly runs: readonly MergeRunRow[];
98
+ readonly now?: string;
99
+ }): MergeSetProjection;
100
+ export type ProjectMergeSetForPlanResult = {
101
+ readonly ok: true;
102
+ readonly projection: MergeSetProjection;
103
+ } | {
104
+ readonly ok: false;
105
+ readonly error: "membership-missing";
106
+ readonly planId: string;
107
+ };
108
+ /**
109
+ * planId → load durable membership + readDashRuns → merge projection.
110
+ * Uses run-registry only (no second run store). Missing membership fails closed.
111
+ * Includes archived runs so archived hosts do not look missing after cleanup.
112
+ */
113
+ export declare function projectMergeSetForPlan(planId: string, options?: {
114
+ readonly cwd?: string;
115
+ readonly now?: string;
116
+ }): Promise<ProjectMergeSetForPlanResult>;
117
+ /** Project merge sets for every durable membership under the state root. */
118
+ export declare function projectAllMergeSets(options?: {
119
+ readonly cwd?: string;
120
+ readonly now?: string;
121
+ }): Promise<readonly MergeSetProjection[]>;
122
+ /** One-line operator rollup for HUD/attention strip. */
123
+ export declare function formatMergeSetSummary(projection: MergeSetProjection): string;
124
+ /** Per-host matrix lines for operator surface. */
125
+ export declare function formatMergeSetMatrix(projection: MergeSetProjection): readonly string[];
126
+ export type MergeOperatorSurface = {
127
+ readonly summary: string;
128
+ readonly matrix: readonly string[];
129
+ /**
130
+ * Explicit scope boundary: this surface is status/summary only.
131
+ * Silent git/PR auto-merge of host worktrees remains out of scope.
132
+ */
133
+ readonly scopeNote: string;
134
+ };
135
+ /** Operator-visible merge surface (HUD/browser). No silent git merge. */
136
+ export declare function formatMergeOperatorSurface(projection: MergeSetProjection): MergeOperatorSurface;
137
+ /** Pick the highest-severity merge projection for a single attention line. */
138
+ export declare function pickPrimaryMergeProjection(projections: readonly MergeSetProjection[]): MergeSetProjection | null;
139
+ /**
140
+ * Attention-strip line for the worst non-ok merge set.
141
+ * all-ok is omitted so generic per-run attention remains visible.
142
+ */
143
+ export declare function formatPrimaryMergeAttentionLine(projections: readonly MergeSetProjection[]): string | null;
@@ -0,0 +1,238 @@
1
+ /**
2
+ * Merge-set projection: membership (expected hosts) + registry runs → rollup.
3
+ * Pure given membership + run observations — no second run store.
4
+ *
5
+ * Operator surface: summary + per-host matrix only. Git/PR auto-merge is out of
6
+ * scope (no silent git merge of host work).
7
+ */
8
+ import { z } from "zod";
9
+ import { projectFanOutAttention, } from "#cli/commands/dash/plan-once-attention.js";
10
+ import { readPlanMembership, listPlanMemberships, } from "#cli/commands/dash/plan-once-membership.js";
11
+ import { readDashRuns } from "#cli/commands/dash/run-registry.js";
12
+ export const MERGE_ROLLUPS = ["all-ok", "partial", "all-failed", "incomplete"];
13
+ /** Rollups that should occupy the attention strip (all-ok does not). */
14
+ export const MERGE_ATTENTION_ROLLUPS = ["incomplete", "partial", "all-failed"];
15
+ export const mergeRollupSchema = z.enum(MERGE_ROLLUPS);
16
+ export const mergeHostStatusSchema = z.enum(["ok", "failed", "missing", "unknown"]);
17
+ export const mergeHostObservationSchema = z
18
+ .object({
19
+ host: z.string().min(1),
20
+ workdir: z.string().min(1),
21
+ expectedLabel: z.string().min(1),
22
+ runId: z.string().nullable(),
23
+ state: z.string().nullable(),
24
+ error: z.string().nullable(),
25
+ status: mergeHostStatusSchema,
26
+ })
27
+ .strict();
28
+ /** Serializable subset of merge projection (attention omitted). */
29
+ export const mergeSetCoreSchema = z
30
+ .object({
31
+ planId: z.string().uuid(),
32
+ expectedCount: z.number().int().nonnegative(),
33
+ observedCount: z.number().int().nonnegative(),
34
+ missingCount: z.number().int().nonnegative(),
35
+ failedCount: z.number().int().nonnegative(),
36
+ okCount: z.number().int().nonnegative(),
37
+ rollup: mergeRollupSchema,
38
+ hosts: z.array(mergeHostObservationSchema),
39
+ })
40
+ .strict();
41
+ function matchRun(expected, runs) {
42
+ // Exact label only — substring/prefix matching can join the wrong host when
43
+ // labels share prefixes and would undercount missing hosts as observed.
44
+ const matches = runs.filter((r) => r.label === expected.label);
45
+ if (matches.length === 0)
46
+ return undefined;
47
+ if (matches.length === 1)
48
+ return matches[0];
49
+ // Prefer newest when re-apply leaves multiple rows with the same label.
50
+ return matches.reduce((best, row) => {
51
+ const bestTs = best.updatedAt ?? "";
52
+ const rowTs = row.updatedAt ?? "";
53
+ return rowTs >= bestTs ? row : best;
54
+ });
55
+ }
56
+ function hostStatus(run) {
57
+ if (!run)
58
+ return "missing";
59
+ const state = run.state.toLowerCase();
60
+ if (state === "failed" || state === "error")
61
+ return "failed";
62
+ if (state === "unknown" || state === "stale")
63
+ return "unknown";
64
+ if (state === "running" ||
65
+ state === "starting" ||
66
+ state === "completed" ||
67
+ state === "idle" ||
68
+ state === "waiting" ||
69
+ state === "detached" ||
70
+ state === "stopped" ||
71
+ state === "archived") {
72
+ return "ok";
73
+ }
74
+ // Unrecognized durable state: fail-closed as unknown (not green)
75
+ return "unknown";
76
+ }
77
+ /**
78
+ * Project merge set from durable membership + registry-visible runs.
79
+ * Missing expected host → incomplete (never all-ok).
80
+ */
81
+ export function projectMergeSet(input) {
82
+ const hosts = input.membership.hosts.map((expected) => {
83
+ const run = matchRun(expected, input.runs);
84
+ const status = hostStatus(run);
85
+ return {
86
+ host: expected.host,
87
+ workdir: expected.workdir,
88
+ expectedLabel: expected.label,
89
+ runId: run?.id ?? null,
90
+ state: run?.state ?? null,
91
+ error: run?.error ?? null,
92
+ status,
93
+ };
94
+ });
95
+ const missingCount = hosts.filter((h) => h.status === "missing").length;
96
+ const failedCount = hosts.filter((h) => h.status === "failed" || h.status === "unknown").length;
97
+ const okCount = hosts.filter((h) => h.status === "ok").length;
98
+ const observedCount = hosts.filter((h) => h.runId !== null).length;
99
+ let rollup;
100
+ if (missingCount > 0) {
101
+ rollup = "incomplete";
102
+ }
103
+ else if (failedCount === hosts.length && hosts.length > 0) {
104
+ rollup = "all-failed";
105
+ }
106
+ else if (failedCount > 0) {
107
+ rollup = "partial";
108
+ }
109
+ else {
110
+ rollup = "all-ok";
111
+ }
112
+ const attentionRuns = hosts
113
+ .filter((h) => h.runId)
114
+ .map((h) => ({
115
+ runId: h.runId,
116
+ provider: h.host,
117
+ state: h.state ?? "unknown",
118
+ error: h.error,
119
+ }));
120
+ const attention = projectFanOutAttention({
121
+ planId: input.membership.planId,
122
+ runs: attentionRuns,
123
+ now: input.now,
124
+ });
125
+ return {
126
+ planId: input.membership.planId,
127
+ expectedCount: hosts.length,
128
+ observedCount,
129
+ missingCount,
130
+ failedCount,
131
+ okCount,
132
+ rollup,
133
+ hosts,
134
+ attention,
135
+ };
136
+ }
137
+ function toMergeRunRows(runs) {
138
+ return runs.map((r) => ({
139
+ id: r.id,
140
+ provider: r.provider,
141
+ state: r.state,
142
+ error: r.error,
143
+ label: r.label,
144
+ updatedAt: r.updatedAt,
145
+ }));
146
+ }
147
+ /**
148
+ * planId → load durable membership + readDashRuns → merge projection.
149
+ * Uses run-registry only (no second run store). Missing membership fails closed.
150
+ * Includes archived runs so archived hosts do not look missing after cleanup.
151
+ */
152
+ export async function projectMergeSetForPlan(planId, options = {}) {
153
+ const membership = await readPlanMembership(planId, { cwd: options.cwd });
154
+ if (!membership) {
155
+ return { ok: false, error: "membership-missing", planId };
156
+ }
157
+ const { runs } = await readDashRuns({ cwd: options.cwd, includeArchived: true });
158
+ const projection = projectMergeSet({
159
+ membership,
160
+ runs: toMergeRunRows(runs),
161
+ now: options.now,
162
+ });
163
+ return { ok: true, projection };
164
+ }
165
+ /** Project merge sets for every durable membership under the state root. */
166
+ export async function projectAllMergeSets(options = {}) {
167
+ const memberships = await listPlanMemberships({ cwd: options.cwd });
168
+ if (memberships.length === 0)
169
+ return [];
170
+ // includeArchived: archived plan-once hosts are still N-of-N observations.
171
+ const { runs } = await readDashRuns({ cwd: options.cwd, includeArchived: true });
172
+ const rows = toMergeRunRows(runs);
173
+ return memberships.map((membership) => projectMergeSet({ membership, runs: rows, now: options.now }));
174
+ }
175
+ /** One-line operator rollup for HUD/attention strip. */
176
+ export function formatMergeSetSummary(projection) {
177
+ const short = projection.planId.slice(0, 8);
178
+ const base = `merge ${short} · ${projection.rollup} · ${projection.okCount}/${projection.expectedCount} ok`;
179
+ // "all-ok" means every expected host is observed and non-failed — not that
180
+ // every host is terminal (running/starting still map to ok for N-of-N).
181
+ if (projection.rollup === "all-ok")
182
+ return `${base} · all observed · none failed`;
183
+ if (projection.missingCount > 0) {
184
+ const missing = projection.hosts
185
+ .filter((h) => h.status === "missing")
186
+ .map((h) => h.host)
187
+ .join(",");
188
+ return `${base} · missing ${missing}`;
189
+ }
190
+ if (projection.failedCount > 0) {
191
+ const failed = projection.hosts
192
+ .filter((h) => h.status === "failed" || h.status === "unknown")
193
+ .map((h) => `${h.host}:${h.error ?? h.state ?? h.status}`)
194
+ .join("; ");
195
+ return `${base} · ${failed}`;
196
+ }
197
+ return base;
198
+ }
199
+ /** Per-host matrix lines for operator surface. */
200
+ export function formatMergeSetMatrix(projection) {
201
+ return projection.hosts.map((h) => {
202
+ const state = h.state ?? "—";
203
+ const err = h.error ? ` · ${h.error}` : "";
204
+ return `${h.host} · ${h.status} · ${state}${err}`;
205
+ });
206
+ }
207
+ /** Operator-visible merge surface (HUD/browser). No silent git merge. */
208
+ export function formatMergeOperatorSurface(projection) {
209
+ return {
210
+ summary: formatMergeSetSummary(projection),
211
+ matrix: formatMergeSetMatrix(projection),
212
+ scopeNote: "git/PR auto-merge out of scope — status/summary only",
213
+ };
214
+ }
215
+ const ROLLUP_SEVERITY = {
216
+ incomplete: 4,
217
+ "all-failed": 3,
218
+ partial: 2,
219
+ "all-ok": 1,
220
+ };
221
+ /** Pick the highest-severity merge projection for a single attention line. */
222
+ export function pickPrimaryMergeProjection(projections) {
223
+ if (projections.length === 0)
224
+ return null;
225
+ return [...projections].sort((a, b) => ROLLUP_SEVERITY[b.rollup] - ROLLUP_SEVERITY[a.rollup])[0];
226
+ }
227
+ /**
228
+ * Attention-strip line for the worst non-ok merge set.
229
+ * all-ok is omitted so generic per-run attention remains visible.
230
+ */
231
+ export function formatPrimaryMergeAttentionLine(projections) {
232
+ const primary = pickPrimaryMergeProjection(projections);
233
+ if (!primary)
234
+ return null;
235
+ if (primary.rollup === "all-ok")
236
+ return null;
237
+ return formatMergeSetSummary(primary);
238
+ }
@@ -0,0 +1,73 @@
1
+ import { z } from "zod";
2
+ import { type DashProvider } from "#cli/commands/dash/provider-capabilities.js";
3
+ /** Bump only with a coordinated migration; validators reject unknown versions. */
4
+ export declare const PLAN_ONCE_SCHEMA_VERSION: 1;
5
+ /** Agent hosts only — never fleet (fleet is a registry-derived provider, not a launch host). */
6
+ export declare const PLAN_ONCE_HOSTS: readonly ["claude", "codex", "grok", "opencode"];
7
+ export type PlanOnceHost = DashProvider;
8
+ export declare const planOnceHostSchema: z.ZodEnum<{
9
+ claude: "claude";
10
+ codex: "codex";
11
+ grok: "grok";
12
+ opencode: "opencode";
13
+ }>;
14
+ export declare const planOnceHostEntrySchema: z.ZodObject<{
15
+ host: z.ZodEnum<{
16
+ claude: "claude";
17
+ codex: "codex";
18
+ grok: "grok";
19
+ opencode: "opencode";
20
+ }>;
21
+ workdir: z.ZodString;
22
+ brief: z.ZodOptional<z.ZodString>;
23
+ label: z.ZodOptional<z.ZodString>;
24
+ extraArgs: z.ZodOptional<z.ZodArray<z.ZodString>>;
25
+ }, z.core.$strict>;
26
+ export type PlanOnceHostEntry = z.infer<typeof planOnceHostEntrySchema>;
27
+ export declare const multiHostPlanProposalSchema: z.ZodObject<{
28
+ schemaVersion: z.ZodLiteral<1>;
29
+ planId: z.ZodString;
30
+ hosts: z.ZodArray<z.ZodObject<{
31
+ host: z.ZodEnum<{
32
+ claude: "claude";
33
+ codex: "codex";
34
+ grok: "grok";
35
+ opencode: "opencode";
36
+ }>;
37
+ workdir: z.ZodString;
38
+ brief: z.ZodOptional<z.ZodString>;
39
+ label: z.ZodOptional<z.ZodString>;
40
+ extraArgs: z.ZodOptional<z.ZodArray<z.ZodString>>;
41
+ }, z.core.$strict>>;
42
+ proposedAt: z.ZodOptional<z.ZodString>;
43
+ }, z.core.$strict>;
44
+ export type MultiHostPlanProposal = z.infer<typeof multiHostPlanProposalSchema>;
45
+ export declare const planOnceDecisionSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
46
+ kind: z.ZodLiteral<"confirm">;
47
+ planId: z.ZodString;
48
+ }, z.core.$strict>, z.ZodObject<{
49
+ kind: z.ZodLiteral<"cancel">;
50
+ planId: z.ZodString;
51
+ }, z.core.$strict>], "kind">;
52
+ export type PlanOnceDecision = z.infer<typeof planOnceDecisionSchema>;
53
+ export type PlanOnceValidationIssue = {
54
+ readonly code: "schema" | "bad-schema-version" | "empty-hosts" | "unknown-host" | "bad-workdir" | "plan-id";
55
+ readonly message: string;
56
+ readonly path?: string;
57
+ };
58
+ export type PlanOnceParseResult = {
59
+ readonly ok: true;
60
+ readonly plan: MultiHostPlanProposal;
61
+ } | {
62
+ readonly ok: false;
63
+ readonly issues: readonly PlanOnceValidationIssue[];
64
+ };
65
+ /** Parse + structural validate an untrusted plan proposal. */
66
+ export declare function parseMultiHostPlanProposal(input: unknown): PlanOnceParseResult;
67
+ export declare function parsePlanOnceDecision(input: unknown): {
68
+ readonly ok: true;
69
+ readonly decision: PlanOnceDecision;
70
+ } | {
71
+ readonly ok: false;
72
+ readonly issues: readonly PlanOnceValidationIssue[];
73
+ };