diff --git a/apps/gittensory-ui/src/components/site/app-panels/maintainer-settings.tsx b/apps/gittensory-ui/src/components/site/app-panels/maintainer-settings.tsx index 1fadbd9fed..dc8a5f4327 100644 --- a/apps/gittensory-ui/src/components/site/app-panels/maintainer-settings.tsx +++ b/apps/gittensory-ui/src/components/site/app-panels/maintainer-settings.tsx @@ -47,17 +47,11 @@ type MaintainerSettings = { agentDryRun: boolean; }; -type AutonomyLevel = "observe" | "suggest" | "propose" | "auto_with_approval" | "auto"; +type AutonomyLevel = "observe" | "auto_with_approval" | "auto"; type AgentActionClass = "review" | "request_changes" | "approve" | "merge" | "close" | "label"; type AutoMergeMethod = "merge" | "squash" | "rebase"; -const AUTONOMY_LEVELS: AutonomyLevel[] = [ - "observe", - "suggest", - "propose", - "auto_with_approval", - "auto", -]; +const AUTONOMY_LEVELS: AutonomyLevel[] = ["observe", "auto_with_approval", "auto"]; const AGENT_ACTION_CLASSES: AgentActionClass[] = [ "review", "request_changes", @@ -492,8 +486,6 @@ export function MaintainerSettings({ reviewability }: { reviewability: Array<{ p
Per-action autonomy: observe (watch only) →{" "}
- suggest →{" "}
- propose →{" "}
auto_with_approval →{" "}
auto. Deny-by-default — anything left at{" "}
observe never acts.
diff --git a/packages/gittensory-engine/src/settings/autonomy.ts b/packages/gittensory-engine/src/settings/autonomy.ts
index b88f97bd9c..2edaab2c62 100644
--- a/packages/gittensory-engine/src/settings/autonomy.ts
+++ b/packages/gittensory-engine/src/settings/autonomy.ts
@@ -2,7 +2,9 @@ import type { AgentActionClass, AutoMaintainPolicy, AutoMergeMethod, AutonomyLev
// The graduated autonomy dial (#773), ordered least → most autonomous. Every later agent-layer phase reads
// this BEFORE acting. `observe` is the deny-by-default floor — gittensory watches but never takes an action.
-export const AUTONOMY_LEVELS = ["observe", "suggest", "propose", "auto_with_approval", "auto"] as const;
+// (#4620: `suggest`/`propose` removed -- both were 100% behaviorally identical to `observe`, see
+// AutonomyLevel's own doc comment.)
+export const AUTONOMY_LEVELS = ["observe", "auto_with_approval", "auto"] as const;
// The write-action classes the maintainer auto-maintain layer (#778) can take on a PR. `review_state_label`
// (#label-scoping) is a separate class from `label`: it gates the planner's own disposition-communication
diff --git a/packages/gittensory-engine/src/types/manifest-deps-types.ts b/packages/gittensory-engine/src/types/manifest-deps-types.ts
index b180147d66..bca40f6e82 100644
--- a/packages/gittensory-engine/src/types/manifest-deps-types.ts
+++ b/packages/gittensory-engine/src/types/manifest-deps-types.ts
@@ -124,7 +124,8 @@ export type ContributorBlacklistEntry = {
addedAt?: string | undefined;
};
-export type AutonomyLevel = "observe" | "suggest" | "propose" | "auto_with_approval" | "auto";
+// (#4620: "suggest"/"propose" removed -- both were 100% behaviorally identical to "observe".)
+export type AutonomyLevel = "observe" | "auto_with_approval" | "auto";
export type AgentActionClass = "review" | "request_changes" | "approve" | "merge" | "close" | "label" | "review_state_label" | "update_branch" | "assign";
diff --git a/src/api/routes.ts b/src/api/routes.ts
index 28afde4c4b..76eb6a6995 100644
--- a/src/api/routes.ts
+++ b/src/api/routes.ts
@@ -761,7 +761,7 @@ const maintainerSettingsSchema = z
}),
// Agent-layer config (#773/#774). The DB layer normalizes both (autonomy: deny-by-default; autoMaintain:
// defaults filled), so a loose record/object here is safe — invalid entries are dropped on persist.
- autonomy: z.record(z.string().trim().min(1).max(32), z.enum(["observe", "suggest", "propose", "auto_with_approval", "auto"])),
+ autonomy: z.record(z.string().trim().min(1).max(32), z.enum(["observe", "auto_with_approval", "auto"])),
autoMaintain: z.object({ requireApprovals: z.number().int().min(0).max(10).optional(), mergeMethod: z.enum(["merge", "squash", "rebase"]).optional() }),
})
.partial();
diff --git a/src/settings/autonomy.ts b/src/settings/autonomy.ts
index 594cf62172..881ea1e256 100644
--- a/src/settings/autonomy.ts
+++ b/src/settings/autonomy.ts
@@ -2,7 +2,9 @@ import type { AgentActionClass, AutoMaintainPolicy, AutoMergeMethod, AutonomyLev
// The graduated autonomy dial (#773), ordered least → most autonomous. Every later agent-layer phase reads
// this BEFORE acting. `observe` is the deny-by-default floor — gittensory watches but never takes an action.
-export const AUTONOMY_LEVELS = ["observe", "suggest", "propose", "auto_with_approval", "auto"] as const;
+// (#4620: `suggest`/`propose` removed -- both were 100% behaviorally identical to `observe`, see
+// AutonomyLevel's own doc comment.)
+export const AUTONOMY_LEVELS = ["observe", "auto_with_approval", "auto"] as const;
// The write-action classes the maintainer auto-maintain layer (#778) can take on a PR. `review_state_label`
// (#label-scoping) is a separate class from `label`: it gates the planner's own disposition-communication
diff --git a/src/types.ts b/src/types.ts
index 215caa9d05..78a04d5c38 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -623,10 +623,9 @@ export type ReviewCheckMode = "required" | "visible" | "disabled";
/** Auto-project/milestone matching (#3183): detects when a PR is likely part of an open GitHub Milestone even
* with no closing-keyword issue link, and posts a bot-comment suggestion. `"off"` (default) runs no matching
- * at all; `"suggest"` matches and posts a single advisory comment, never mutating the PR; `"auto"` is accepted
- * by config today but behaves identically to `"suggest"` until #3185 wires real milestone attachment -- no
- * attach/auto-apply code exists yet, so treating it as inert-but-silent would be a worse failure mode than
- * degrading to the safe, visible suggest behavior. */
+ * at all; `"suggest"` matches and posts a single advisory comment, never mutating the PR; `"auto"` (#3185,
+ * shipped) actually calls `attachToMilestone`/`attachToProject` for a high-confidence match instead of only
+ * commenting -- see `maybeSuggestMilestoneMatchForPr` in `integrations/project-tracker-adapter.ts`. */
export type ProjectMilestoneMatchMode = "off" | "suggest" | "auto";
/** Which backend {@link ProjectMilestoneMatchMode} matches against (#3186). `"github"` (default) uses the
@@ -1285,9 +1284,12 @@ export type ContributorBlacklistEntry = {
};
/** Agent-layer graduated autonomy (#773), least → most autonomous. `observe` is the deny-by-default floor:
- * gittensory watches but never acts. `suggest`/`propose` surface guidance/concrete proposals without
- * executing; `auto_with_approval` executes behind a human approval gate (#779); `auto` executes directly. */
-export type AutonomyLevel = "observe" | "suggest" | "propose" | "auto_with_approval" | "auto";
+ * gittensory watches but never acts. `auto_with_approval` executes behind a human approval gate (#779);
+ * `auto` executes directly. (#4620: `suggest`/`propose` were removed here -- the doc comment promised
+ * distinct "surface guidance/concrete proposals without executing" behavior, but every read site
+ * (`isActingAutonomyLevel`/`autonomyRequiresApproval`) only ever distinguished acting from non-acting, so
+ * both were 100% behaviorally identical to `observe` from day one. No stored config used either value.) */
+export type AutonomyLevel = "observe" | "auto_with_approval" | "auto";
/** The write-action classes the maintainer auto-maintain layer (#778) can take on a PR. `label` gates the
* anti-abuse enforcement labels tied 1:1 to a `close` in the same disposition (blacklist/contributor-cap/
diff --git a/test/unit/agent-actions.test.ts b/test/unit/agent-actions.test.ts
index 072fb56387..8054bec2ff 100644
--- a/test/unit/agent-actions.test.ts
+++ b/test/unit/agent-actions.test.ts
@@ -42,7 +42,7 @@ describe("planAgentMaintenanceActions (#778)", () => {
});
it("plans nothing when every class is at a non-acting level", () => {
- const plan = planAgentMaintenanceActions(input({ conclusion: "failure", autonomy: { review_state_label: "suggest", request_changes: "propose", close: "observe" }, blockerTitles: ["x"] }));
+ const plan = planAgentMaintenanceActions(input({ conclusion: "failure", autonomy: { review_state_label: "observe", request_changes: "observe", close: "observe" }, blockerTitles: ["x"] }));
expect(plan).toEqual([]);
});
diff --git a/test/unit/agent-execution.test.ts b/test/unit/agent-execution.test.ts
index f98136483c..99c13092c1 100644
--- a/test/unit/agent-execution.test.ts
+++ b/test/unit/agent-execution.test.ts
@@ -146,7 +146,7 @@ describe("agent write-permission readiness (#775)", () => {
expect(agentRequiresPrWrite({ request_changes: "auto_with_approval" })).toBe(true);
expect(agentRequiresPrWrite({ close: "auto" })).toBe(true);
// non-acting levels never demand write
- expect(agentRequiresPrWrite({ merge: "propose", review: "suggest" })).toBe(false);
+ expect(agentRequiresPrWrite({ merge: "observe", review: "observe" })).toBe(false);
expect(agentRequiresPrWrite({ merge: "observe" })).toBe(false);
expect(agentRequiresPrWrite({})).toBe(false);
expect(agentRequiresPrWrite(null)).toBe(false);
diff --git a/test/unit/autonomy-engine.test.ts b/test/unit/autonomy-engine.test.ts
index 25729a4119..2db630ce01 100644
--- a/test/unit/autonomy-engine.test.ts
+++ b/test/unit/autonomy-engine.test.ts
@@ -54,8 +54,6 @@ describe("autonomy level predicates", () => {
it("isActingAutonomyLevel is true only for auto / auto_with_approval", () => {
expect(isActingAutonomyLevel("auto")).toBe(true);
expect(isActingAutonomyLevel("auto_with_approval")).toBe(true);
- expect(isActingAutonomyLevel("propose")).toBe(false);
- expect(isActingAutonomyLevel("suggest")).toBe(false);
expect(isActingAutonomyLevel("observe")).toBe(false);
});
@@ -68,13 +66,13 @@ describe("autonomy level predicates", () => {
it("the level ladder is ordered observe → … → auto with observe at the floor", () => {
expect(AUTONOMY_LEVELS[0]).toBe("observe");
expect(AUTONOMY_LEVELS[AUTONOMY_LEVELS.length - 1]).toBe("auto");
- expect(AUTONOMY_LEVELS).toEqual(["observe", "suggest", "propose", "auto_with_approval", "auto"]);
+ expect(AUTONOMY_LEVELS).toEqual(["observe", "auto_with_approval", "auto"]);
});
});
describe("normalizeAutonomyPolicy", () => {
it("keeps only known action classes mapped to known levels", () => {
- expect(normalizeAutonomyPolicy({ merge: "auto", review: "suggest" })).toEqual({ merge: "auto", review: "suggest" });
+ expect(normalizeAutonomyPolicy({ merge: "auto", review: "auto_with_approval" })).toEqual({ merge: "auto", review: "auto_with_approval" });
});
it("drops unknown action classes and unknown levels (deny-by-omission)", () => {
@@ -91,7 +89,7 @@ describe("normalizeAutonomyPolicy", () => {
});
it("round-trips a valid policy through normalization", () => {
- const policy: AutonomyPolicy = { review: "propose", request_changes: "auto_with_approval", merge: "observe" };
+ const policy: AutonomyPolicy = { review: "auto", request_changes: "auto_with_approval", merge: "observe" };
expect(normalizeAutonomyPolicy(policy)).toEqual(policy);
});
});
@@ -124,11 +122,11 @@ describe("isAgentConfigured (#777 opt-in detection)", () => {
it("is true when any action class has an acting level", () => {
expect(isAgentConfigured({ merge: "auto" })).toBe(true);
expect(isAgentConfigured({ label: "auto_with_approval" })).toBe(true);
- expect(isAgentConfigured({ review: "suggest", close: "auto" })).toBe(true);
+ expect(isAgentConfigured({ review: "observe", close: "auto" })).toBe(true);
});
it("is false for the deny-by-default floor (all observe / non-acting / empty / null)", () => {
- expect(isAgentConfigured({ merge: "observe", review: "suggest", approve: "propose" })).toBe(false);
+ expect(isAgentConfigured({ merge: "observe", review: "observe", approve: "observe" })).toBe(false);
expect(isAgentConfigured({})).toBe(false);
expect(isAgentConfigured(null)).toBe(false);
expect(isAgentConfigured(undefined)).toBe(false);
diff --git a/test/unit/autonomy.test.ts b/test/unit/autonomy.test.ts
index b8b3d79edb..7fb828d3dc 100644
--- a/test/unit/autonomy.test.ts
+++ b/test/unit/autonomy.test.ts
@@ -53,8 +53,6 @@ describe("autonomy level predicates", () => {
it("isActingAutonomyLevel is true only for auto / auto_with_approval", () => {
expect(isActingAutonomyLevel("auto")).toBe(true);
expect(isActingAutonomyLevel("auto_with_approval")).toBe(true);
- expect(isActingAutonomyLevel("propose")).toBe(false);
- expect(isActingAutonomyLevel("suggest")).toBe(false);
expect(isActingAutonomyLevel("observe")).toBe(false);
});
@@ -67,13 +65,13 @@ describe("autonomy level predicates", () => {
it("the level ladder is ordered observe → … → auto with observe at the floor", () => {
expect(AUTONOMY_LEVELS[0]).toBe("observe");
expect(AUTONOMY_LEVELS[AUTONOMY_LEVELS.length - 1]).toBe("auto");
- expect(AUTONOMY_LEVELS).toEqual(["observe", "suggest", "propose", "auto_with_approval", "auto"]);
+ expect(AUTONOMY_LEVELS).toEqual(["observe", "auto_with_approval", "auto"]);
});
});
describe("normalizeAutonomyPolicy", () => {
it("keeps only known action classes mapped to known levels", () => {
- expect(normalizeAutonomyPolicy({ merge: "auto", review: "suggest" })).toEqual({ merge: "auto", review: "suggest" });
+ expect(normalizeAutonomyPolicy({ merge: "auto", review: "auto_with_approval" })).toEqual({ merge: "auto", review: "auto_with_approval" });
});
it("drops unknown action classes and unknown levels (deny-by-omission)", () => {
@@ -90,7 +88,7 @@ describe("normalizeAutonomyPolicy", () => {
});
it("round-trips a valid policy through normalization", () => {
- const policy: AutonomyPolicy = { review: "propose", request_changes: "auto_with_approval", merge: "observe" };
+ const policy: AutonomyPolicy = { review: "auto", request_changes: "auto_with_approval", merge: "observe" };
expect(normalizeAutonomyPolicy(policy)).toEqual(policy);
});
});
@@ -123,11 +121,11 @@ describe("isAgentConfigured (#777 opt-in detection)", () => {
it("is true when any action class has an acting level", () => {
expect(isAgentConfigured({ merge: "auto" })).toBe(true);
expect(isAgentConfigured({ label: "auto_with_approval" })).toBe(true);
- expect(isAgentConfigured({ review: "suggest", close: "auto" })).toBe(true);
+ expect(isAgentConfigured({ review: "observe", close: "auto" })).toBe(true);
});
it("is false for the deny-by-default floor (all observe / non-acting / empty / null)", () => {
- expect(isAgentConfigured({ merge: "observe", review: "suggest", approve: "propose" })).toBe(false);
+ expect(isAgentConfigured({ merge: "observe", review: "observe", approve: "observe" })).toBe(false);
expect(isAgentConfigured({})).toBe(false);
expect(isAgentConfigured(null)).toBe(false);
expect(isAgentConfigured(undefined)).toBe(false);
diff --git a/test/unit/config-templates.test.ts b/test/unit/config-templates.test.ts
index fd2e18fb18..c30e7eb3ed 100644
--- a/test/unit/config-templates.test.ts
+++ b/test/unit/config-templates.test.ts
@@ -227,10 +227,10 @@ describe("config/examples review templates (#1682)", () => {
expect(resolveAutonomy(def.settings.autonomy, "review")).toBe("observe");
// Explicit per-action levels parse; each set action resolves to its level, unset actions stay "observe" —
// the parity the config-generator's autonomy-level dial emits into.
- const on = parseFocusManifest({ settings: { autonomy: { review: "suggest", merge: "auto" } } });
- expect(on.settings.autonomy).toEqual({ review: "suggest", merge: "auto" });
+ const on = parseFocusManifest({ settings: { autonomy: { review: "auto_with_approval", merge: "auto" } } });
+ expect(on.settings.autonomy).toEqual({ review: "auto_with_approval", merge: "auto" });
expect(isAgentConfigured(on.settings.autonomy)).toBe(true);
- expect(resolveAutonomy(on.settings.autonomy, "review")).toBe("suggest");
+ expect(resolveAutonomy(on.settings.autonomy, "review")).toBe("auto_with_approval");
expect(resolveAutonomy(on.settings.autonomy, "merge")).toBe("auto");
expect(resolveAutonomy(on.settings.autonomy, "close")).toBe("observe"); // unset action ⇒ default
});
@@ -248,7 +248,7 @@ describe("config/examples review templates (#1682)", () => {
" - dist/**",
"settings:",
" autonomy:",
- " review: suggest",
+ " review: auto_with_approval",
].join("\n");
const imported = parseFocusManifestContent(yml, "repo_file");
expect(imported.warnings).toEqual([]);
@@ -257,6 +257,6 @@ describe("config/examples review templates (#1682)", () => {
expect(imported.gate.linkedIssue).toBe("block");
expect(imported.review.inlineComments).toBe(true);
expect(imported.review.excludePaths).toEqual(["dist/**"]);
- expect(resolveAutonomy(imported.settings.autonomy, "review")).toBe("suggest");
+ expect(resolveAutonomy(imported.settings.autonomy, "review")).toBe("auto_with_approval");
});
});
diff --git a/test/unit/effective-config-summary.test.ts b/test/unit/effective-config-summary.test.ts
index 172854288a..c6e0c99ad4 100644
--- a/test/unit/effective-config-summary.test.ts
+++ b/test/unit/effective-config-summary.test.ts
@@ -50,7 +50,7 @@ describe("summarizeEffectiveConfig", () => {
it("never leaks a secret/reward/trust/wallet field (public-safe, #2168 house rule)", () => {
const out = summarizeEffectiveConfig(
- base({ autonomy: { review: "auto", request_changes: "propose", approve: "auto_with_approval", merge: "auto", close: "suggest" } }),
+ base({ autonomy: { review: "auto", request_changes: "observe", approve: "auto_with_approval", merge: "auto", close: "observe" } }),
"live",
).toLowerCase();
for (const banned of ["reward", "payout", "emission", "wallet", "hotkey", "coldkey", "privatekey", "trustscore", "rawtrust", "coldkeys", "secret"]) {