@cat-factory/app 0.156.0 → 0.158.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.
@@ -5,7 +5,7 @@
5
5
  // task inspector's "Merge policy" dropdown selects from. Exactly one preset is the
6
6
  // default; it cannot be deleted or un-defaulted (the backend enforces this too).
7
7
  import { computed, reactive, ref, watch } from 'vue'
8
- import type { RiskPolicy, RequirementConcernLevel } from '~/types/merge'
8
+ import type { MergeClassRules, RiskPolicy, RequirementConcernLevel } from '~/types/merge'
9
9
  import type { StepGating } from '@cat-factory/contracts'
10
10
 
11
11
  const { t } = useI18n()
@@ -43,6 +43,9 @@ interface Draft {
43
43
  maxRequirementIterations: number
44
44
  maxRequirementConcernAllowed: RequirementConcernLevel
45
45
  autoMergeEnabled: boolean
46
+ // Per-change-class auto-merge rules. An OMITTED class means "use the score ceilings above",
47
+ // so `{}` is the identity — the editor stores `thresholds` as an omission for that reason.
48
+ classRules: MergeClassRules
46
49
  // Implementation-fork decision gating (edited 0..100, stored 0..1); disabled ⇒ off in `auto`.
47
50
  forkEnabled: boolean
48
51
  forkMinComplexity: number
@@ -79,6 +82,7 @@ function toDraft(p: RiskPolicy): Draft {
79
82
  maxRequirementIterations: p.maxRequirementIterations,
80
83
  maxRequirementConcernAllowed: p.maxRequirementConcernAllowed,
81
84
  autoMergeEnabled: p.autoMergeEnabled,
85
+ classRules: { ...p.classRules },
82
86
  forkEnabled: p.forkDecision?.enabled ?? false,
83
87
  forkMinComplexity: Math.round((p.forkDecision?.minComplexity ?? 0.5) * 100),
84
88
  forkMinRisk: Math.round((p.forkDecision?.minRisk ?? 0.4) * 100),
@@ -121,6 +125,7 @@ async function save(p: RiskPolicy) {
121
125
  maxRequirementIterations: d.maxRequirementIterations,
122
126
  maxRequirementConcernAllowed: d.maxRequirementConcernAllowed,
123
127
  autoMergeEnabled: d.autoMergeEnabled,
128
+ classRules: d.classRules,
124
129
  forkDecision: forkGating(d),
125
130
  })
126
131
  toast.add({
@@ -176,6 +181,7 @@ const draft = reactive<Draft>({
176
181
  maxRequirementIterations: 6,
177
182
  maxRequirementConcernAllowed: 'none',
178
183
  autoMergeEnabled: true,
184
+ classRules: {},
179
185
  forkEnabled: false,
180
186
  forkMinComplexity: 50,
181
187
  forkMinRisk: 40,
@@ -196,10 +202,12 @@ async function create() {
196
202
  maxRequirementIterations: draft.maxRequirementIterations,
197
203
  maxRequirementConcernAllowed: draft.maxRequirementConcernAllowed,
198
204
  autoMergeEnabled: draft.autoMergeEnabled,
205
+ classRules: draft.classRules,
199
206
  forkDecision: forkGating(draft),
200
207
  })
201
208
  draft.name = ''
202
209
  draft.autoMergeEnabled = true
210
+ draft.classRules = {}
203
211
  toast.add({
204
212
  title: t('settings.riskPolicy.toast.created'),
205
213
  icon: 'i-lucide-check',
@@ -341,6 +349,16 @@ async function create() {
341
349
  </label>
342
350
  </div>
343
351
 
352
+ <!-- Per-change-class auto-merge rules, each shown beside that class's accumulated track
353
+ record — the number that justifies widening the rule. -->
354
+ <div class="mt-3 rounded-md border border-slate-800 bg-slate-900/40 p-3">
355
+ <MergeClassRulesEditor
356
+ v-model="drafts[p.id]!.classRules"
357
+ :auto-merge-enabled="drafts[p.id]!.autoMergeEnabled"
358
+ :disabled="busy === p.id"
359
+ />
360
+ </div>
361
+
344
362
  <!-- Implementation-fork decision gate: propose materially different approaches before the
345
363
  Coder writes code (in `auto` tri-state, gated on the task estimate). -->
346
364
  <div class="mt-3 rounded-md border border-slate-800 bg-slate-900/40 p-3">
@@ -32,6 +32,7 @@ const back = useIntegrationBack(open)
32
32
  const ROUTABLE = computed<{ type: NotificationType; label: string }[]>(() => [
33
33
  { type: 'merge_review', label: t('slack.routable.merge_review') },
34
34
  { type: 'pipeline_complete', label: t('slack.routable.pipeline_complete') },
35
+ { type: 'merge_tag_request', label: t('slack.routable.merge_tag_request') },
35
36
  { type: 'ci_failed', label: t('slack.routable.ci_failed') },
36
37
  { type: 'test_failed', label: t('slack.routable.test_failed') },
37
38
  { type: 'requirement_review', label: t('slack.routable.requirement_review') },
@@ -54,6 +55,7 @@ const ROLE_OPTIONS = computed<{ label: string; value: SlackMemberRole }[]>(() =>
54
55
  const routes = reactive<Record<NotificationType, SlackRoute>>({
55
56
  merge_review: { enabled: false, channel: '' },
56
57
  pipeline_complete: { enabled: false, channel: '' },
58
+ merge_tag_request: { enabled: false, channel: '' },
57
59
  ci_failed: { enabled: false, channel: '' },
58
60
  test_failed: { enabled: false, channel: '' },
59
61
  requirement_review: { enabled: false, channel: '' },
@@ -17,6 +17,7 @@ import {
17
17
  } from '@cat-factory/contracts'
18
18
  import type { RequestStepChangesInput } from '@cat-factory/contracts'
19
19
  import type { IterationCapChoice } from '~/types/execution'
20
+ import type { ReviewEffort } from '~/types/merge'
20
21
  import type { ApiContext } from './context'
21
22
 
22
23
  /** Run lifecycle (start/cancel/decisions/approvals/restart) + LLM metrics + spend. */
@@ -38,8 +39,14 @@ export function executionApi({ send, sendWith, ws, pwHeaders }: ApiContext) {
38
39
  cancelExecution: (workspaceId: string, blockId: string) =>
39
40
  send(cancelExecutionContract, { pathPrefix: ws(workspaceId), pathParams: { blockId } }),
40
41
 
41
- mergeBlock: (workspaceId: string, blockId: string) =>
42
- send(mergeBlockContract, { pathPrefix: ws(workspaceId), pathParams: { blockId } }),
42
+ // `reviewEffort` records the reviewer-effort tag onto the block's merge track record in the
43
+ // same request as the merge (see the notification `act` counterpart). Always optional.
44
+ mergeBlock: (workspaceId: string, blockId: string, reviewEffort?: ReviewEffort | null) =>
45
+ send(mergeBlockContract, {
46
+ pathPrefix: ws(workspaceId),
47
+ pathParams: { blockId },
48
+ body: reviewEffort === undefined ? {} : { reviewEffort },
49
+ }),
43
50
 
44
51
  resolveDecision: (
45
52
  workspaceId: string,
@@ -3,6 +3,7 @@ import {
3
3
  dismissNotificationContract,
4
4
  listNotificationsContract,
5
5
  } from '@cat-factory/contracts'
6
+ import type { ReviewEffort } from '~/types/merge'
6
7
  import type { ApiContext } from './context'
7
8
 
8
9
  /** The human-actionable notification inbox (act / dismiss). */
@@ -12,11 +13,14 @@ export function notificationsApi({ send, ws }: ApiContext) {
12
13
  listNotifications: (workspaceId: string) =>
13
14
  send(listNotificationsContract, { pathPrefix: ws(workspaceId) }),
14
15
 
15
- // Act on a notification (merge the PR / confirm / retry), then resolve it.
16
- actNotification: (workspaceId: string, id: string) =>
16
+ // Act on a notification (merge the PR / confirm / retry), then resolve it. On a merge card,
17
+ // `reviewEffort` records how much review the PR actually needed onto the run's merge track
18
+ // record in the SAME request — always optional (an untagged merge records a null tag).
19
+ actNotification: (workspaceId: string, id: string, reviewEffort?: ReviewEffort | null) =>
17
20
  send(actNotificationContract, {
18
21
  pathPrefix: ws(workspaceId),
19
22
  pathParams: { notificationId: id },
23
+ body: reviewEffort === undefined ? {} : { reviewEffort },
20
24
  }),
21
25
 
22
26
  // Dismiss a notification without acting.
@@ -1,5 +1,7 @@
1
1
  import {
2
2
  createRiskPolicyContract,
3
+ listMergeClassRollupsContract,
4
+ tagMergeReviewEffortContract,
3
5
  createModelPresetContract,
4
6
  deleteRiskPolicyContract,
5
7
  deleteModelPresetContract,
@@ -10,7 +12,7 @@ import {
10
12
  updateRiskPolicyContract,
11
13
  updateModelPresetContract,
12
14
  } from '@cat-factory/contracts'
13
- import type { UpdateRiskPolicyInput } from '~/types/merge'
15
+ import type { ReviewEffort, UpdateRiskPolicyInput } from '~/types/merge'
14
16
  import type { CreateModelPresetInput, UpdateModelPresetInput } from '~/types/model-presets'
15
17
  import type { SendParams } from './client'
16
18
  import type { ApiContext } from './context'
@@ -45,6 +47,24 @@ export function presetsApi({ send, ws }: ApiContext) {
45
47
  reseedRiskPolicy: (workspaceId: string, presetId: string) =>
46
48
  send(reseedRiskPolicyContract, { pathPrefix: ws(workspaceId), pathParams: { presetId } }),
47
49
 
50
+ // ---- merge track record (the per-class evidence behind the policy) -----
51
+ // Every class in ONE request (a single SQL aggregate server-side), so the preset editor can
52
+ // show each class's rule next to the numbers that justify widening it without fanning out.
53
+ listMergeClassRollups: (workspaceId: string) =>
54
+ send(listMergeClassRollupsContract, { pathPrefix: ws(workspaceId) }),
55
+
56
+ // Tag (or clear) how much review a merged PR actually needed.
57
+ tagMergeReviewEffort: (
58
+ workspaceId: string,
59
+ recordId: string,
60
+ reviewEffort: ReviewEffort | null,
61
+ ) =>
62
+ send(tagMergeReviewEffortContract, {
63
+ pathPrefix: ws(workspaceId),
64
+ pathParams: { recordId },
65
+ body: { reviewEffort },
66
+ }),
67
+
48
68
  // ---- model presets (per-task model->agent mapping library) ------------
49
69
  listModelPresets: (workspaceId: string) =>
50
70
  send(listModelPresetsContract, { pathPrefix: ws(workspaceId) }),
@@ -2,6 +2,7 @@ import type { Ref } from 'vue'
2
2
  import type { ExecutionInstance, Pipeline } from '~/types/domain'
3
3
  import type { RequestStepChangesInput } from '@cat-factory/contracts'
4
4
  import type { IterationCapChoice } from '~/types/execution'
5
+ import type { ReviewEffort } from '~/types/merge'
5
6
  import { useWorkspaceStore } from '~/stores/workspace'
6
7
 
7
8
  /**
@@ -125,11 +126,15 @@ export function createExecutionCommands(ctx: ExecutionCommandContext) {
125
126
  })
126
127
  }
127
128
 
128
- /** Merge an open PR (a task in `pr_ready`) — the server completes the task. */
129
- async function mergePr(blockId: string) {
129
+ /**
130
+ * Merge an open PR (a task in `pr_ready`) — the server completes the task. `reviewEffort` records
131
+ * how much review the PR actually needed onto its merge track record in the same request; always
132
+ * optional (an untagged merge records a null tag and nothing downstream breaks).
133
+ */
134
+ async function mergePr(blockId: string, reviewEffort?: ReviewEffort | null) {
130
135
  const ws = useWorkspaceStore()
131
136
  try {
132
- await api.mergeBlock(ws.requireId(), blockId)
137
+ await api.mergeBlock(ws.requireId(), blockId, reviewEffort)
133
138
  await ws.refresh()
134
139
  } catch (e) {
135
140
  runErrors.present(e, 'errors.action.mergeFailed')
@@ -0,0 +1,61 @@
1
+ import { CHANGE_CLASSES, emptyMergeClassRollup } from '@cat-factory/contracts'
2
+ import { defineStore } from 'pinia'
3
+ import { computed, ref } from 'vue'
4
+ import type { ChangeClass, MergeClassRollup, ReviewEffort } from '~/types/merge'
5
+ import { useWorkspaceStore } from '~/stores/workspace'
6
+
7
+ /**
8
+ * The merge track record's per-change-class rollups — the accumulated evidence a workspace widens
9
+ * its per-class auto-merge rules against, plus the reviewer-effort tag surface.
10
+ *
11
+ * Loaded on demand (NOT from the workspace snapshot): the rollups are a settings-screen and
12
+ * merge-card concern, not board state, and they change only when a merge settles. The whole set
13
+ * arrives in ONE request — a single SQL aggregate server-side — so the preset editor never fans
14
+ * out per class.
15
+ */
16
+ export const useMergeTrackRecordsStore = defineStore('mergeTrackRecords', () => {
17
+ const api = useApi()
18
+
19
+ const rollups = ref<MergeClassRollup[]>([])
20
+ const loading = ref(false)
21
+ /** Whether a load has completed at least once, so the UI can tell "empty" from "not yet". */
22
+ const loaded = ref(false)
23
+
24
+ /**
25
+ * Every class keyed for lookup, filled in with zeros for a class the workspace has no records
26
+ * for. The backend already returns the full set; this keeps the UI total even before the first
27
+ * load resolves.
28
+ */
29
+ const byClass = computed<Record<ChangeClass, MergeClassRollup>>(() => {
30
+ const found = new Map(rollups.value.map((r) => [r.changeClass, r]))
31
+ return Object.fromEntries(
32
+ CHANGE_CLASSES.map((c) => [c, found.get(c) ?? emptyMergeClassRollup(c)]),
33
+ ) as Record<ChangeClass, MergeClassRollup>
34
+ })
35
+
36
+ /** Whether the workspace has ANY track record yet (drives the "no data yet" hint). */
37
+ const hasData = computed(() => rollups.value.some((r) => r.total > 0))
38
+
39
+ async function load(): Promise<void> {
40
+ const ws = useWorkspaceStore()
41
+ loading.value = true
42
+ try {
43
+ rollups.value = await api.listMergeClassRollups(ws.requireId())
44
+ loaded.value = true
45
+ } finally {
46
+ loading.value = false
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Tag (or clear) how much review a merged PR needed, then refresh the rollups so the class's
52
+ * effort distribution reflects it immediately.
53
+ */
54
+ async function tag(recordId: string, reviewEffort: ReviewEffort | null): Promise<void> {
55
+ const ws = useWorkspaceStore()
56
+ await api.tagMergeReviewEffort(ws.requireId(), recordId, reviewEffort)
57
+ if (loaded.value) await load()
58
+ }
59
+
60
+ return { rollups, byClass, hasData, loading, loaded, load, tag }
61
+ })
@@ -1,6 +1,7 @@
1
1
  import { defineStore } from 'pinia'
2
2
  import { computed } from 'vue'
3
3
  import type { Notification } from '~/types/domain'
4
+ import type { ReviewEffort } from '~/types/merge'
4
5
  import { useUpsertList } from '~/composables/useUpsertList'
5
6
  import { useWorkspaceStore } from '~/stores/workspace'
6
7
 
@@ -53,10 +54,16 @@ export const useNotificationsStore = defineStore('notifications', () => {
53
54
  /** Total open count, for the toolbar badge. */
54
55
  const count = computed(() => open.value.length)
55
56
 
56
- /** Act on a notification (merge / confirm / retry); the board patches via the event. */
57
- async function act(id: string) {
57
+ /**
58
+ * Act on a notification (merge / confirm / retry); the board patches via the event.
59
+ *
60
+ * `reviewEffort` is the merge card's one-tap "how much review did this need?" answer, recorded
61
+ * onto the run's merge track record in the same request. Always optional: acting without it
62
+ * merges exactly as before and leaves the record's tag null.
63
+ */
64
+ async function act(id: string, reviewEffort?: ReviewEffort | null) {
58
65
  const ws = useWorkspaceStore()
59
- const resolved = await api.actNotification(ws.requireId(), id)
66
+ const resolved = await api.actNotification(ws.requireId(), id, reviewEffort)
60
67
  upsert(resolved)
61
68
  // The action (merge/confirm/retry) changed block/run state — reconcile fully.
62
69
  await ws.refresh()
@@ -18,6 +18,7 @@ export const useTrackerStore = defineStore('tracker', () => {
18
18
  linearTeamId: null,
19
19
  writebackCommentOnPrOpen: false,
20
20
  writebackResolveOnMerge: false,
21
+ writebackQuestionsOnPark: false,
21
22
  updatedAt: 0,
22
23
  })
23
24
 
@@ -31,6 +32,7 @@ export const useTrackerStore = defineStore('tracker', () => {
31
32
  linearTeamId: null,
32
33
  writebackCommentOnPrOpen: false,
33
34
  writebackResolveOnMerge: false,
35
+ writebackQuestionsOnPark: false,
34
36
  updatedAt: 0,
35
37
  }
36
38
  }
@@ -6,6 +6,13 @@
6
6
 
7
7
  export type {
8
8
  MergeAssessment,
9
+ // Merge track record — the per-class human evidence behind the auto-merge policy.
10
+ ChangeClass,
11
+ MergeClassRollup,
12
+ MergeClassRule,
13
+ MergeClassRules,
14
+ MergeTrackRecord,
15
+ ReviewEffort,
9
16
  RequirementConcernLevel,
10
17
  RiskPolicy,
11
18
  CreateRiskPolicyInput,
@@ -350,6 +350,10 @@
350
350
  "resolveOnMerge": {
351
351
  "label": "Beim Mergen eines PR als gelöst schließen",
352
352
  "help": "Kommentiere, dass der PR gemergt wurde, und schließe / löse dann das verknüpfte Issue."
353
+ },
354
+ "questionsOnPark": {
355
+ "label": "Offene Fragen bei einem pausierten Headless-Lauf posten",
356
+ "help": "Wenn ein über die API gestarteter Lauf zur Klärung der Anforderungen pausiert, poste seine offenen Fragen — jeweils mit der ID, die eine Antwort nennt — am verknüpften Issue. In der App gestartete Aufgaben sind nicht betroffen."
353
357
  }
354
358
  },
355
359
  "probeFailure": {
@@ -451,6 +455,19 @@
451
455
  "run": "Trotzdem vorschlagen",
452
456
  "skip": "Überspringen"
453
457
  }
458
+ },
459
+ "classRules": {
460
+ "heading": "Regeln je Änderungskategorie",
461
+ "help": "Jeder Pull Request wird anhand der geänderten Dateien klassifiziert. Eine Kategorie kann immer automatisch zusammengeführt werden, immer ein Review verlangen oder auf die Schwellenwerte oben zurückfallen. Die Zahlen zeigen, was diese Kategorie bisher gebraucht hat.",
462
+ "autoMergeOffWarning": "Für diese Vorlage ist das automatische Zusammenführen aus, daher geht jeder Pull Request weiterhin an einen Menschen. Eine Kategorieregel kann das nicht überschreiben.",
463
+ "noData": "Noch keine Historie",
464
+ "record": "{merged} zusammengeführt, {autoShare} automatisch",
465
+ "frictionless": "{share} ohne Anmerkungen",
466
+ "rule": {
467
+ "thresholds": "Schwellenwerte verwenden",
468
+ "always": "Immer automatisch zusammenführen",
469
+ "never": "Immer Review verlangen"
470
+ }
454
471
  }
455
472
  },
456
473
  "observabilityConnection": {
@@ -1317,6 +1334,7 @@
1317
1334
  "issueWriteback": "Issue-Rückschreibung",
1318
1335
  "commentOnPrOpen": "Kommentar beim PR-Öffnen",
1319
1336
  "closeOnMerge": "Beim Mergen schließen",
1337
+ "questionsOnPark": "Fragen beim Pausieren stellen",
1320
1338
  "writebackHint": "Schreibt zurück in das verknüpfte Tracker-Issue dieser Aufgabe. Überschreibt den Workspace-Standard.",
1321
1339
  "responsibleProduct": "Verantwortliches Produkt",
1322
1340
  "responsibleEmpty": "Nicht zugewiesen. Setze einen Product Owner, um ihn zu benachrichtigen, wenn die Anforderungsprüfung diese Aufgabe kennzeichnet.",
@@ -1401,7 +1419,9 @@
1401
1419
  "no_rationale": "Der Merger hat den PR bewertet, aber keine Begründung gegeben, daher konnte dem Urteil zum automatischen Mergen nicht vertraut werden; der PR wartet darauf, dass ein Mensch mergt.",
1402
1420
  "no_assessment": "Der Merger hat keine parsbare Bewertung zurückgegeben, daher wartet der PR darauf, dass ein Mensch mergt.",
1403
1421
  "merge_failed": "Die Bewertungen lagen innerhalb der {preset}-Schwellenwerte, aber der automatische Merge konnte nicht abgeschlossen werden (zum Beispiel Branch-Schutz oder ein Konflikt), daher wartet der PR darauf, dass ein Mensch mergt.",
1404
- "merge_partial": "Einige der Pull Requests der Aufgabe wurden gemergt, aber ein späterer nicht, daher wartet die Änderung darauf, dass ein Mensch den Multi-Repo-Merge abschließt oder zurückrollt."
1422
+ "merge_partial": "Einige der Pull Requests der Aufgabe wurden gemergt, aber ein späterer nicht, daher wartet die Änderung darauf, dass ein Mensch den Multi-Repo-Merge abschließt oder zurückrollt.",
1423
+ "class_auto_merge": "Diese Vorlage führt diese Änderungskategorie immer automatisch zusammen, daher wurden die Bewertungen nicht verglichen.",
1424
+ "class_requires_review": "Diese Vorlage gibt diese Änderungskategorie unabhängig von den Bewertungen immer an einen Menschen."
1405
1425
  },
1406
1426
  "scores": "Bewertungen",
1407
1427
  "axis": {
@@ -1813,7 +1833,8 @@
1813
1833
  "fork_decision_pending": "Als gelesen markieren",
1814
1834
  "pr_review_ready": "Als gelesen markieren",
1815
1835
  "budget_paused": "Als gelesen markieren",
1816
- "key_drift": "Veraltete Zugangsdaten entfernen"
1836
+ "key_drift": "Veraltete Zugangsdaten entfernen",
1837
+ "merge_tag_request": "Aufwand erfassen"
1817
1838
  }
1818
1839
  },
1819
1840
  "aiProvidersBanner": {
@@ -4242,7 +4263,8 @@
4242
4263
  "human_test_ready": "Bereit für manuelle Tests",
4243
4264
  "visual_confirmation_ready": "Bereit für visuelle Bestätigung",
4244
4265
  "pr_review_ready": "PR-Review-Befunde",
4245
- "initiative": "Initiativen-Updates"
4266
+ "initiative": "Initiativen-Updates",
4267
+ "merge_tag_request": "Review-Aufwand angefragt"
4246
4268
  },
4247
4269
  "role": {
4248
4270
  "engineering": "Engineering",
@@ -5183,6 +5205,24 @@
5183
5205
  "unlinkSourceFailed": "Quelle konnte nicht getrennt werden"
5184
5206
  }
5185
5207
  },
5208
+ "merge": {
5209
+ "changeClass": {
5210
+ "docs": "Doku & Texte",
5211
+ "test": "Tests",
5212
+ "dependency": "Abhängigkeits-Update",
5213
+ "config": "Konfiguration & CI",
5214
+ "source": "Quellcode",
5215
+ "schema": "Schema & Migrationen",
5216
+ "unknown": "Nicht klassifiziert"
5217
+ },
5218
+ "effort": {
5219
+ "prompt": "Review-Aufwand",
5220
+ "none": "Keine Anmerkungen",
5221
+ "minor": "Kleine Anmerkungen",
5222
+ "major": "Echte Nacharbeit",
5223
+ "classHistory": "{merged} in dieser Kategorie zusammengeführt, {autoShare} automatisch"
5224
+ }
5225
+ },
5186
5226
  "vcs": {
5187
5227
  "panel": {
5188
5228
  "title": "Quellcodeverwaltung",
@@ -1088,6 +1088,7 @@
1088
1088
  "issueWriteback": "Issue writeback",
1089
1089
  "commentOnPrOpen": "Comment on PR open",
1090
1090
  "closeOnMerge": "Close on merge",
1091
+ "questionsOnPark": "Ask questions on park",
1091
1092
  "writebackHint": "Writes back to this task's linked tracker issue. Overrides the workspace default.",
1092
1093
  "responsibleProduct": "Responsible product",
1093
1094
  "responsibleEmpty": "Unassigned. Set a product owner to notify them when requirement review flags this task.",
@@ -1172,7 +1173,9 @@
1172
1173
  "no_rationale": "The merger scored the PR but gave no rationale, so the verdict could not be trusted to auto-merge; the PR is waiting for a human to merge.",
1173
1174
  "no_assessment": "The merger did not return a parseable assessment, so the PR is waiting for a human to merge.",
1174
1175
  "merge_failed": "The scores were within the {preset} thresholds, but the automatic merge could not complete (for example branch protection or a conflict), so the PR is waiting for a human to merge.",
1175
- "merge_partial": "Some of the task's pull requests merged, but a later one could not, so the change is waiting for a human to finish or revert the multi-repo merge."
1176
+ "merge_partial": "Some of the task's pull requests merged, but a later one could not, so the change is waiting for a human to finish or revert the multi-repo merge.",
1177
+ "class_auto_merge": "This preset always auto-merges this class of change, so the scores were not compared.",
1178
+ "class_requires_review": "This preset always sends this class of change to a human, whatever the scores."
1176
1179
  },
1177
1180
  "scores": "Scores",
1178
1181
  "axis": {
@@ -1789,7 +1792,8 @@
1789
1792
  "fork_decision_pending": "Mark read",
1790
1793
  "pr_review_ready": "Mark read",
1791
1794
  "budget_paused": "Mark read",
1792
- "key_drift": "Drop stale credentials"
1795
+ "key_drift": "Drop stale credentials",
1796
+ "merge_tag_request": "Record effort"
1793
1797
  }
1794
1798
  },
1795
1799
  "aiProvidersBanner": {
@@ -2416,6 +2420,10 @@
2416
2420
  "resolveOnMerge": {
2417
2421
  "label": "Close as resolved when a PR merges",
2418
2422
  "help": "Comment that the PR merged, then close / resolve the linked issue."
2423
+ },
2424
+ "questionsOnPark": {
2425
+ "label": "Post open questions on a parked headless run",
2426
+ "help": "When a run started through the API pauses to clarify requirements, post its open questions — each with the id an answer names — on the linked issue. Tasks started in the app are unaffected."
2419
2427
  }
2420
2428
  },
2421
2429
  "probeFailure": {
@@ -2517,6 +2525,25 @@
2517
2525
  "run": "Propose anyway",
2518
2526
  "skip": "Skip"
2519
2527
  }
2528
+ },
2529
+ "classRules": {
2530
+ "heading": "Rules per change class",
2531
+ "help": "Each pull request is classified from the files it changed. A class can always auto-merge, always require review, or fall back to the score ceilings above. The counts show what that class has needed so far.",
2532
+ "autoMergeOffWarning": "Auto-merge is off for this preset, so every pull request still goes to a human. A class rule cannot override that.",
2533
+ "noData": "No history yet",
2534
+ "record": "{merged} merged, {autoShare} automatically",
2535
+ "@record": {
2536
+ "description": "'{autoShare}' is already a formatted percentage; do not add a % sign."
2537
+ },
2538
+ "frictionless": "{share} needed no comments",
2539
+ "@frictionless": {
2540
+ "description": "'{share}' is already a formatted percentage; do not add a % sign."
2541
+ },
2542
+ "rule": {
2543
+ "thresholds": "Use score ceilings",
2544
+ "always": "Always auto-merge",
2545
+ "never": "Always require review"
2546
+ }
2520
2547
  }
2521
2548
  },
2522
2549
  "observabilityConnection": {
@@ -3347,7 +3374,8 @@
3347
3374
  "human_test_ready": "Ready for human testing",
3348
3375
  "visual_confirmation_ready": "Ready for visual confirmation",
3349
3376
  "pr_review_ready": "PR review findings",
3350
- "initiative": "Initiative updates"
3377
+ "initiative": "Initiative updates",
3378
+ "merge_tag_request": "Review-effort tag requested"
3351
3379
  },
3352
3380
  "role": {
3353
3381
  "engineering": "Engineering",
@@ -5312,6 +5340,36 @@
5312
5340
  "unlinkSourceFailed": "Could not unlink source"
5313
5341
  }
5314
5342
  },
5343
+ "merge": {
5344
+ "changeClass": {
5345
+ "docs": "Docs & copy",
5346
+ "test": "Tests",
5347
+ "dependency": "Dependency bump",
5348
+ "@dependency": {
5349
+ "description": "A change that only bumps third-party package versions (a 'dependency bump'), not a change that depends on something."
5350
+ },
5351
+ "config": "Config & CI",
5352
+ "source": "Source code",
5353
+ "@source": {
5354
+ "description": "'Source' here means program source code (as opposed to docs/config/tests), NOT the origin of something."
5355
+ },
5356
+ "schema": "Schema & migrations",
5357
+ "unknown": "Unclassified"
5358
+ },
5359
+ "effort": {
5360
+ "prompt": "Review effort",
5361
+ "none": "No comments",
5362
+ "@none": {
5363
+ "description": "Answer to 'how much review effort did this PR need?': means the reviewer had NOTHING to say, not that no review happened."
5364
+ },
5365
+ "minor": "Minor notes",
5366
+ "major": "Real rework",
5367
+ "classHistory": "{merged} merged in this class, {autoShare} auto-merged",
5368
+ "@classHistory": {
5369
+ "description": "'{autoShare}' is already a formatted percentage; do not add a % sign."
5370
+ }
5371
+ }
5372
+ },
5315
5373
  "vcs": {
5316
5374
  "panel": {
5317
5375
  "title": "Source control",
@@ -1031,6 +1031,7 @@
1031
1031
  "issueWriteback": "Reescritura de incidencias",
1032
1032
  "commentOnPrOpen": "Comentar al abrir el PR",
1033
1033
  "closeOnMerge": "Cerrar al fusionar",
1034
+ "questionsOnPark": "Preguntar al pausarse",
1034
1035
  "writebackHint": "Reescribe en la incidencia del tracker vinculada a esta tarea. Anula el valor predeterminado del espacio de trabajo.",
1035
1036
  "responsibleProduct": "Producto responsable",
1036
1037
  "responsibleEmpty": "Sin asignar. Define un responsable de producto para notificarle cuando la revisión de requisitos marque esta tarea.",
@@ -1115,7 +1116,9 @@
1115
1116
  "no_rationale": "El fusionador puntuó el PR pero no dio ninguna justificación, así que no se pudo confiar en el veredicto para fusionar automáticamente; el PR espera a que una persona lo fusione.",
1116
1117
  "no_assessment": "El fusionador no devolvió una evaluación analizable, por lo que el PR espera a que una persona lo fusione.",
1117
1118
  "merge_failed": "Las puntuaciones estaban dentro de los umbrales de {preset}, pero la fusión automática no pudo completarse (por ejemplo, protección de rama o un conflicto), por lo que el PR espera a que una persona lo fusione.",
1118
- "merge_partial": "Algunas de las solicitudes de incorporación de la tarea se fusionaron, pero una posterior no pudo, por lo que el cambio espera a que una persona termine o revierta la fusión multirrepositorio."
1119
+ "merge_partial": "Algunas de las solicitudes de incorporación de la tarea se fusionaron, pero una posterior no pudo, por lo que el cambio espera a que una persona termine o revierta la fusión multirrepositorio.",
1120
+ "class_auto_merge": "Este preajuste fusiona siempre esta categoría de cambio de forma automática, así que no se compararon las puntuaciones.",
1121
+ "class_requires_review": "Este preajuste envía siempre esta categoría de cambio a una persona, sean cuales sean las puntuaciones."
1119
1122
  },
1120
1123
  "scores": "Puntuaciones",
1121
1124
  "axis": {
@@ -1720,7 +1723,8 @@
1720
1723
  "fork_decision_pending": "Marcar como leído",
1721
1724
  "pr_review_ready": "Marcar como leída",
1722
1725
  "budget_paused": "Marcar como leída",
1723
- "key_drift": "Descartar credenciales obsoletas"
1726
+ "key_drift": "Descartar credenciales obsoletas",
1727
+ "merge_tag_request": "Registrar esfuerzo"
1724
1728
  },
1725
1729
  "toast": {
1726
1730
  "acted": "Marcado como resuelto",
@@ -2228,6 +2232,10 @@
2228
2232
  "resolveOnMerge": {
2229
2233
  "label": "Cerrar como resuelta cuando se fusiona un PR",
2230
2234
  "help": "Comenta que el PR se fusionó y luego cierra / resuelve la incidencia vinculada."
2235
+ },
2236
+ "questionsOnPark": {
2237
+ "label": "Publicar preguntas abiertas cuando un ejecución headless se detiene",
2238
+ "help": "Cuando una ejecución iniciada por la API se detiene para aclarar requisitos, publica sus preguntas abiertas — cada una con el id que debe nombrar la respuesta — en la incidencia vinculada. Las tareas iniciadas en la aplicación no se ven afectadas."
2231
2239
  }
2232
2240
  },
2233
2241
  "probeFailure": {
@@ -2329,6 +2337,19 @@
2329
2337
  "run": "Proponer igualmente",
2330
2338
  "skip": "Omitir"
2331
2339
  }
2340
+ },
2341
+ "classRules": {
2342
+ "heading": "Reglas por categoría de cambio",
2343
+ "help": "Cada pull request se clasifica a partir de los archivos que modificó. Una categoría puede fusionarse siempre de forma automática, exigir siempre revisión, o usar los umbrales de arriba. Los recuentos muestran lo que esa categoría ha necesitado hasta ahora.",
2344
+ "autoMergeOffWarning": "El auto-merge está desactivado en este preajuste, así que cada pull request sigue pasando por una persona. Una regla de categoría no puede anular eso.",
2345
+ "noData": "Aún sin historial",
2346
+ "record": "{merged} fusionados, {autoShare} automáticamente",
2347
+ "frictionless": "{share} no necesitó comentarios",
2348
+ "rule": {
2349
+ "thresholds": "Usar los umbrales",
2350
+ "always": "Fusionar siempre automáticamente",
2351
+ "never": "Exigir siempre revisión"
2352
+ }
2332
2353
  }
2333
2354
  },
2334
2355
  "observabilityConnection": {
@@ -3251,7 +3272,8 @@
3251
3272
  "human_test_ready": "Listo para pruebas humanas",
3252
3273
  "visual_confirmation_ready": "Listo para confirmacion visual",
3253
3274
  "pr_review_ready": "Hallazgos de revisión de PR",
3254
- "initiative": "Actualizaciones de la iniciativa"
3275
+ "initiative": "Actualizaciones de la iniciativa",
3276
+ "merge_tag_request": "Solicitud de esfuerzo de revisión"
3255
3277
  },
3256
3278
  "role": {
3257
3279
  "engineering": "Ingenieria",
@@ -5171,6 +5193,24 @@
5171
5193
  "unlinkSourceFailed": "No se pudo desvincular la fuente"
5172
5194
  }
5173
5195
  },
5196
+ "merge": {
5197
+ "changeClass": {
5198
+ "docs": "Documentación y textos",
5199
+ "test": "Pruebas",
5200
+ "dependency": "Actualización de dependencias",
5201
+ "config": "Configuración y CI",
5202
+ "source": "Código fuente",
5203
+ "schema": "Esquema y migraciones",
5204
+ "unknown": "Sin clasificar"
5205
+ },
5206
+ "effort": {
5207
+ "prompt": "Esfuerzo de revisión",
5208
+ "none": "Sin comentarios",
5209
+ "minor": "Comentarios menores",
5210
+ "major": "Retrabajo real",
5211
+ "classHistory": "{merged} fusionados en esta categoría, {autoShare} de forma automática"
5212
+ }
5213
+ },
5174
5214
  "vcs": {
5175
5215
  "panel": {
5176
5216
  "title": "Control de código fuente",