@tomismeta/paperclip-aperture 0.2.1 → 0.3.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 +32 -3
- package/dist/manifest.js +2 -1
- package/dist/ui/index.js +145 -18
- package/dist/worker.js +196 -67
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -64,22 +64,43 @@ and agents attention now? actually sees to th
|
|
|
64
64
|
|
|
65
65
|
- a Focus surface inside Paperclip
|
|
66
66
|
- ranked `now`, `next`, and `ambient` attention lanes
|
|
67
|
+
- embedded explainability in the Focus UI, including `Why now`, `Why next`, confidence, signals, thread context, and related activity
|
|
67
68
|
- approval handling, including budget-specific approval semantics
|
|
68
69
|
- issue-aware operator language such as `review required`, `blocked`, and targeted recommended moves
|
|
69
70
|
- agent-aware routing that distinguishes known company agents from human/operator roles when issue text references them
|
|
70
71
|
- a plugin-local deterministic semantic mapping layer that interprets Paperclip issue, approval, and agent signals before publishing them into Aperture Core
|
|
72
|
+
- richer semantic continuity hints on mapped issue events, including `supersedes` and `resolves` relationships where Paperclip-specific intent is clear
|
|
73
|
+
- document-aware review interpretation for memo/spec-backed issues so Focus can tell the difference between `review is blocked on the artifact` and `the artifact is attached, monitor instead`
|
|
71
74
|
- dynamic re-stacking so items can move between `now`, `next`, and `ambient` as new evidence arrives
|
|
72
75
|
- inline issue commenting from the Focus surface when a Paperclip issue supports written response
|
|
73
76
|
- durable acknowledge/suppression behavior backed by plugin state and ledger replay
|
|
74
77
|
- a sidebar entry, page, and dashboard widget
|
|
75
78
|
|
|
79
|
+
## Explainability
|
|
80
|
+
|
|
81
|
+
Focus is meant to be more legible than a smart inbox.
|
|
82
|
+
|
|
83
|
+
The first `0.3.0` explainability slice keeps reasoning attached to the cards you are already acting on:
|
|
84
|
+
|
|
85
|
+
- `Why now` on the active card explains why the current item outranks the rest of the queue
|
|
86
|
+
- `Why next` on queued rows explains why something is staged behind the current top item
|
|
87
|
+
- `Confidence` shows how strong the semantic signal is when the plugin has one
|
|
88
|
+
- `Signals` surfaces the specific factors that pushed the item into attention
|
|
89
|
+
- `Thread context` and `Related activity` help explain whether an item is part of a broader episode or continuation
|
|
90
|
+
|
|
91
|
+
The intent is not to expose every internal scoring detail. It is to help an operator answer:
|
|
92
|
+
|
|
93
|
+
- why did this surface?
|
|
94
|
+
- why is it in this lane?
|
|
95
|
+
- how much should I trust this judgment?
|
|
96
|
+
|
|
76
97
|
## Package Boundary
|
|
77
98
|
|
|
78
99
|
This plugin treats Paperclip as the host runtime and UI shell, while embedding [Aperture Core](https://github.com/tomismeta/aperture/tree/main/packages/core) through the npm package [`@tomismeta/aperture-core`](https://www.npmjs.com/package/@tomismeta/aperture-core).
|
|
79
100
|
|
|
80
101
|
It is a pure SDK integration: Aperture Core is used as-is inside a self-contained Paperclip plugin, with no changes to Aperture Core or Paperclip core.
|
|
81
102
|
|
|
82
|
-
For `0.
|
|
103
|
+
For `0.3.x`, the boundary works like this:
|
|
83
104
|
|
|
84
105
|
- the plugin worker owns Aperture ingestion, replay, review state, and display composition
|
|
85
106
|
- Paperclip remains the system of record for issue and approval writes
|
|
@@ -104,14 +125,19 @@ Before releasing, run:
|
|
|
104
125
|
pnpm release:check
|
|
105
126
|
```
|
|
106
127
|
|
|
107
|
-
For a live local Paperclip smoke test:
|
|
128
|
+
For a live local Paperclip smoke test, start Paperclip first:
|
|
108
129
|
|
|
109
130
|
```bash
|
|
110
131
|
pnpm build
|
|
132
|
+
npx paperclipai run -i default
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Then, in a second terminal:
|
|
136
|
+
|
|
137
|
+
```bash
|
|
111
138
|
npx paperclipai context set --api-base http://localhost:3100
|
|
112
139
|
npx paperclipai plugin uninstall tomismeta.paperclip-aperture --force
|
|
113
140
|
npx paperclipai plugin install --local .
|
|
114
|
-
npx paperclipai run -i default
|
|
115
141
|
```
|
|
116
142
|
|
|
117
143
|
Then open `http://127.0.0.1:3100/APE/aperture` and verify:
|
|
@@ -121,6 +147,9 @@ Then open `http://127.0.0.1:3100/APE/aperture` and verify:
|
|
|
121
147
|
- issue comments post successfully from Focus
|
|
122
148
|
- approval actions update Focus correctly
|
|
123
149
|
- resolved blocker comments downgrade stale `Now` items
|
|
150
|
+
- attached issue documents downgrade stale `share the memo/spec` review blockers into monitor-only follow-up
|
|
151
|
+
- the active card exposes `Why now`
|
|
152
|
+
- expanded queued rows expose `Why next`
|
|
124
153
|
|
|
125
154
|
## Links
|
|
126
155
|
|
package/dist/manifest.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const manifest = {
|
|
2
2
|
id: "tomismeta.paperclip-aperture",
|
|
3
3
|
apiVersion: 1,
|
|
4
|
-
version: "0.
|
|
4
|
+
version: "0.3.0",
|
|
5
5
|
displayName: "Paperclip Aperture",
|
|
6
6
|
description: "The live attention layer for Paperclip, powered by Aperture's deterministic attention engine.",
|
|
7
7
|
author: "@tomismeta",
|
|
@@ -12,6 +12,7 @@ const manifest = {
|
|
|
12
12
|
"issues.read",
|
|
13
13
|
"issue.comments.read",
|
|
14
14
|
"issue.comments.create",
|
|
15
|
+
"issue.documents.read",
|
|
15
16
|
"plugin.state.read",
|
|
16
17
|
"plugin.state.write",
|
|
17
18
|
"ui.dashboardWidget.register",
|
package/dist/ui/index.js
CHANGED
|
@@ -123,6 +123,82 @@ function mergeStoredFrames(snapshot, companyId, candidates, review, nowIso = (/*
|
|
|
123
123
|
}, review);
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
+
// src/aperture/explainability.ts
|
|
127
|
+
function asRecord(value) {
|
|
128
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
129
|
+
}
|
|
130
|
+
function readStringArray(value) {
|
|
131
|
+
if (!Array.isArray(value)) return [];
|
|
132
|
+
return value.filter((entry) => typeof entry === "string" && entry.trim().length > 0);
|
|
133
|
+
}
|
|
134
|
+
function readSemanticConfidence(frame) {
|
|
135
|
+
const semantic = asRecord(frame.metadata?.semantic);
|
|
136
|
+
const confidence = semantic?.confidence;
|
|
137
|
+
return confidence === "low" || confidence === "medium" || confidence === "high" ? confidence : null;
|
|
138
|
+
}
|
|
139
|
+
function readSemanticRelationHints(frame) {
|
|
140
|
+
const semantic = asRecord(frame.metadata?.semantic);
|
|
141
|
+
const relationHints = semantic?.relationHints;
|
|
142
|
+
if (!Array.isArray(relationHints)) return [];
|
|
143
|
+
return relationHints.filter((entry) => {
|
|
144
|
+
if (typeof entry !== "object" || entry === null) return false;
|
|
145
|
+
const hint = entry;
|
|
146
|
+
return hint.kind === "same_issue" || hint.kind === "resolves" || hint.kind === "supersedes" || hint.kind === "repeats" || hint.kind === "escalates";
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
function relationHintLabel(hint) {
|
|
150
|
+
switch (hint.kind) {
|
|
151
|
+
case "same_issue":
|
|
152
|
+
return "Part of the same thread";
|
|
153
|
+
case "resolves":
|
|
154
|
+
return "Resolves an earlier blocker";
|
|
155
|
+
case "supersedes":
|
|
156
|
+
return "Moves the request forward";
|
|
157
|
+
case "repeats":
|
|
158
|
+
return "Repeats an earlier ask";
|
|
159
|
+
case "escalates":
|
|
160
|
+
return "Raises the urgency";
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
function laneReason(lane) {
|
|
164
|
+
switch (lane) {
|
|
165
|
+
case "active":
|
|
166
|
+
return "This is the most urgent item in the queue right now.";
|
|
167
|
+
case "queued":
|
|
168
|
+
return "This is queued behind the current top item.";
|
|
169
|
+
case "ambient":
|
|
170
|
+
return "This is visible for awareness without needing action yet.";
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
function continuitySummary(frame) {
|
|
174
|
+
const episode = asRecord(frame.metadata?.episode);
|
|
175
|
+
const size = typeof episode?.size === "number" ? episode.size : null;
|
|
176
|
+
const state = typeof episode?.state === "string" ? episode.state.replace(/_/g, " ") : null;
|
|
177
|
+
if (!size || size <= 1) return null;
|
|
178
|
+
if (state) return `Part of a ${state} thread with ${size} related interactions.`;
|
|
179
|
+
return `Part of a thread with ${size} related interactions.`;
|
|
180
|
+
}
|
|
181
|
+
function frameSignals(frame) {
|
|
182
|
+
const attention = asRecord(frame.metadata?.attention);
|
|
183
|
+
const rationale = readStringArray(attention?.rationale);
|
|
184
|
+
const factors = readStringArray(frame.provenance?.factors);
|
|
185
|
+
return [.../* @__PURE__ */ new Set([...rationale.length > 0 ? rationale : factors, ...factors])];
|
|
186
|
+
}
|
|
187
|
+
function signalStrengthLabel(confidence) {
|
|
188
|
+
return `${confidence} confidence`;
|
|
189
|
+
}
|
|
190
|
+
function explainFrame(frame, lane) {
|
|
191
|
+
const relationLabels = [...new Set(readSemanticRelationHints(frame).map(relationHintLabel))];
|
|
192
|
+
return {
|
|
193
|
+
whyNow: frame.provenance?.whyNow ?? frame.summary ?? null,
|
|
194
|
+
laneReason: laneReason(lane),
|
|
195
|
+
signalStrength: readSemanticConfidence(frame),
|
|
196
|
+
signals: frameSignals(frame),
|
|
197
|
+
relationLabels,
|
|
198
|
+
continuity: continuitySummary(frame)
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
126
202
|
// src/aperture/attention-context.ts
|
|
127
203
|
var ATTENTION_CONTEXT_IDS = {
|
|
128
204
|
approvalType: "approval-type",
|
|
@@ -223,6 +299,10 @@ function approvalRecordToFrame(record) {
|
|
|
223
299
|
const decisionContext = typeof payload.decisionContext === "string" ? payload.decisionContext : void 0;
|
|
224
300
|
const summary = typeof payload.summary === "string" ? payload.summary : approvalBlockingSummary(budgetOverride);
|
|
225
301
|
const updatedAt = record.updatedAt ?? record.createdAt;
|
|
302
|
+
const provenance = {
|
|
303
|
+
whyNow: approvalBlockingWhyNow(budgetOverride),
|
|
304
|
+
factors: budgetOverride ? ["budget stop", "approval", "operator decision"] : ["approval", "operator decision"]
|
|
305
|
+
};
|
|
226
306
|
return {
|
|
227
307
|
id: `approval-bootstrap:${record.id}`,
|
|
228
308
|
taskId: `approval:${record.id}`,
|
|
@@ -256,17 +336,20 @@ function approvalRecordToFrame(record) {
|
|
|
256
336
|
...budgetOverride ? [{ id: "request-revision", label: "Request revision", kind: "cancel", emphasis: "secondary" }] : []
|
|
257
337
|
]
|
|
258
338
|
},
|
|
259
|
-
provenance
|
|
260
|
-
whyNow: approvalBlockingWhyNow(budgetOverride),
|
|
261
|
-
factors: budgetOverride ? ["budget stop", "approval", "operator decision"] : ["approval", "operator decision"]
|
|
262
|
-
},
|
|
339
|
+
provenance,
|
|
263
340
|
timing: {
|
|
264
341
|
createdAt: record.createdAt,
|
|
265
342
|
updatedAt
|
|
266
343
|
},
|
|
267
344
|
metadata: {
|
|
268
345
|
approvalStatus: record.status,
|
|
269
|
-
approvalType: record.type
|
|
346
|
+
approvalType: record.type,
|
|
347
|
+
attention: {
|
|
348
|
+
rationale: provenance.factors
|
|
349
|
+
},
|
|
350
|
+
semantic: {
|
|
351
|
+
confidence: "high"
|
|
352
|
+
}
|
|
270
353
|
}
|
|
271
354
|
};
|
|
272
355
|
}
|
|
@@ -933,6 +1016,52 @@ function ContextItems({ frame }) {
|
|
|
933
1016
|
item2.id
|
|
934
1017
|
)) });
|
|
935
1018
|
}
|
|
1019
|
+
function DetailCard(props) {
|
|
1020
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("border border-border bg-secondary/50 px-3 py-2", props.className), children: [
|
|
1021
|
+
/* @__PURE__ */ jsx("div", { className: "text-xs font-medium text-muted-foreground", children: props.label }),
|
|
1022
|
+
/* @__PURE__ */ jsx("div", { className: "mt-1", children: props.children })
|
|
1023
|
+
] });
|
|
1024
|
+
}
|
|
1025
|
+
function ExplainabilityBadges(props) {
|
|
1026
|
+
if (props.values.length === 0) return null;
|
|
1027
|
+
return /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center gap-2", children: props.values.map((value) => /* @__PURE__ */ jsx(
|
|
1028
|
+
Badge,
|
|
1029
|
+
{
|
|
1030
|
+
className: props.accent ? "border-transparent" : "border-border bg-background/70 text-foreground/80",
|
|
1031
|
+
style: props.accent ? { color: ACCENT_COLOR, backgroundColor: `${ACCENT_COLOR}14`, borderColor: `${ACCENT_COLOR}33` } : void 0,
|
|
1032
|
+
children: value
|
|
1033
|
+
},
|
|
1034
|
+
value
|
|
1035
|
+
)) });
|
|
1036
|
+
}
|
|
1037
|
+
function ExplainabilityPanel(props) {
|
|
1038
|
+
const explanation = explainFrame(props.frame, props.lane);
|
|
1039
|
+
const strength = explanation.signalStrength ? signalStrengthLabel(explanation.signalStrength) : null;
|
|
1040
|
+
return /* @__PURE__ */ jsxs("div", { className: "grid gap-3 lg:grid-cols-2", children: [
|
|
1041
|
+
/* @__PURE__ */ jsxs(DetailCard, { label: "Why now", className: "lg:col-span-2", children: [
|
|
1042
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm leading-relaxed text-foreground/90", children: explanation.whyNow ?? judgmentLine(props.frame, props.lane) }),
|
|
1043
|
+
/* @__PURE__ */ jsx("p", { className: "mt-2 text-xs leading-relaxed text-muted-foreground", children: explanation.laneReason })
|
|
1044
|
+
] }),
|
|
1045
|
+
strength ? /* @__PURE__ */ jsx(DetailCard, { label: "Confidence", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-foreground/90", children: strength }) }) : null,
|
|
1046
|
+
explanation.continuity ? /* @__PURE__ */ jsx(DetailCard, { label: "Related activity", children: /* @__PURE__ */ jsx("p", { className: "text-sm leading-relaxed text-foreground/90", children: explanation.continuity }) }) : null,
|
|
1047
|
+
explanation.signals.length > 0 ? /* @__PURE__ */ jsx(DetailCard, { label: "Signals", className: explanation.relationLabels.length > 0 ? "lg:col-span-1" : "lg:col-span-2", children: /* @__PURE__ */ jsx(ExplainabilityBadges, { values: explanation.signals }) }) : null,
|
|
1048
|
+
explanation.relationLabels.length > 0 ? /* @__PURE__ */ jsx(DetailCard, { label: "Thread context", className: explanation.signals.length > 0 ? "lg:col-span-1" : "lg:col-span-2", children: /* @__PURE__ */ jsx(ExplainabilityBadges, { values: explanation.relationLabels, accent: true }) }) : null
|
|
1049
|
+
] });
|
|
1050
|
+
}
|
|
1051
|
+
function ExplainabilityStrip(props) {
|
|
1052
|
+
const explanation = explainFrame(props.frame, props.lane);
|
|
1053
|
+
const chips = [
|
|
1054
|
+
...explanation.signalStrength ? [signalStrengthLabel(explanation.signalStrength)] : [],
|
|
1055
|
+
...explanation.signals.slice(0, 2),
|
|
1056
|
+
...explanation.relationLabels.slice(0, 1)
|
|
1057
|
+
];
|
|
1058
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-2 rounded-md border border-border/70 bg-secondary/30 px-3 py-2", children: [
|
|
1059
|
+
/* @__PURE__ */ jsx("div", { className: "text-xs font-medium text-muted-foreground", children: props.lane === "queued" ? "Why next" : props.lane === "ambient" ? "Why ambient" : "Why now" }),
|
|
1060
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs leading-relaxed text-muted-foreground", children: explanation.whyNow ?? judgmentLine(props.frame, props.lane) }),
|
|
1061
|
+
/* @__PURE__ */ jsx("div", { className: "text-[11px] leading-relaxed text-muted-foreground", children: explanation.laneReason }),
|
|
1062
|
+
chips.length > 0 ? /* @__PURE__ */ jsx(ExplainabilityBadges, { values: chips, accent: true }) : null
|
|
1063
|
+
] });
|
|
1064
|
+
}
|
|
936
1065
|
function isIssueFrame(frame) {
|
|
937
1066
|
return entityTypeFromFrame(frame) === "issue";
|
|
938
1067
|
}
|
|
@@ -1017,19 +1146,16 @@ function NowDetails(props) {
|
|
|
1017
1146
|
const budgetHref = isBudgetOverride(frame) ? costsHref(props.companyPrefix) : null;
|
|
1018
1147
|
const activityLink = activityHref(frame, props.companyPrefix);
|
|
1019
1148
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-3 border-t border-border pt-4", children: [
|
|
1020
|
-
/* @__PURE__ */
|
|
1021
|
-
|
|
1022
|
-
/* @__PURE__ */ jsx("
|
|
1023
|
-
/* @__PURE__ */
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
requestedAmount(frame)
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
] }) : null,
|
|
1031
|
-
budgetReason(frame) ? /* @__PURE__ */ jsx("span", { children: budgetReason(frame) }) : null
|
|
1032
|
-
] })
|
|
1149
|
+
/* @__PURE__ */ jsx(ExplainabilityPanel, { frame, lane: "active" }),
|
|
1150
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground", children: [
|
|
1151
|
+
/* @__PURE__ */ jsx("span", { children: requestDescriptor(frame) }),
|
|
1152
|
+
/* @__PURE__ */ jsx("span", { children: impactLabel(frame) }),
|
|
1153
|
+
/* @__PURE__ */ jsx("span", { children: formatRelativeTime(frameUpdatedAt(frame, snapshotUpdatedAt)) }),
|
|
1154
|
+
requestedAmount(frame) ? /* @__PURE__ */ jsxs("span", { children: [
|
|
1155
|
+
"Amount requested: ",
|
|
1156
|
+
requestedAmount(frame)
|
|
1157
|
+
] }) : null,
|
|
1158
|
+
budgetReason(frame) ? /* @__PURE__ */ jsx("span", { children: budgetReason(frame) }) : null
|
|
1033
1159
|
] }),
|
|
1034
1160
|
/* @__PURE__ */ jsx(ContextItems, { frame }),
|
|
1035
1161
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [
|
|
@@ -1311,6 +1437,7 @@ function NextRow(props) {
|
|
|
1311
1437
|
),
|
|
1312
1438
|
expanded ? /* @__PURE__ */ jsxs("div", { className: "space-y-3 px-4 pb-3 pl-12 sm:px-6 sm:pl-14", children: [
|
|
1313
1439
|
showDetailText ? /* @__PURE__ */ jsx("p", { className: "text-sm leading-relaxed text-muted-foreground", children: detailText }) : null,
|
|
1440
|
+
/* @__PURE__ */ jsx(ExplainabilityStrip, { frame, lane: "queued" }),
|
|
1314
1441
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-muted-foreground", children: [
|
|
1315
1442
|
/* @__PURE__ */ jsx("span", { children: impactLabel(frame) }),
|
|
1316
1443
|
driver ? /* @__PURE__ */ jsx(Accent, { children: driver }) : null,
|
package/dist/worker.js
CHANGED
|
@@ -12330,12 +12330,78 @@ function truncate(value, max = 180) {
|
|
|
12330
12330
|
function issueText(issue, comment) {
|
|
12331
12331
|
return [comment?.body, issue.description].filter((value) => !!value).join("\n");
|
|
12332
12332
|
}
|
|
12333
|
+
function toTimestamp(value) {
|
|
12334
|
+
if (!value) return 0;
|
|
12335
|
+
if (value instanceof Date) return value.getTime();
|
|
12336
|
+
const timestamp = Date.parse(value);
|
|
12337
|
+
return Number.isNaN(timestamp) ? 0 : timestamp;
|
|
12338
|
+
}
|
|
12339
|
+
function toIsoString(value) {
|
|
12340
|
+
if (!value) return null;
|
|
12341
|
+
if (typeof value === "string") return value;
|
|
12342
|
+
return value.toISOString();
|
|
12343
|
+
}
|
|
12333
12344
|
function normalizeActorKey(value) {
|
|
12334
12345
|
return value.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
12335
12346
|
}
|
|
12336
12347
|
function escapeRegex(value) {
|
|
12337
12348
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
12338
12349
|
}
|
|
12350
|
+
function issueRelationTarget(issueId) {
|
|
12351
|
+
return `issue:${issueId}`;
|
|
12352
|
+
}
|
|
12353
|
+
function detectIssueIntents(text) {
|
|
12354
|
+
const intents = /* @__PURE__ */ new Set();
|
|
12355
|
+
for (const detector of ISSUE_INTENT_DETECTORS) {
|
|
12356
|
+
if (detector.pattern.test(text)) intents.add(detector.key);
|
|
12357
|
+
}
|
|
12358
|
+
return intents;
|
|
12359
|
+
}
|
|
12360
|
+
function blockingTargetForText(text, identifier) {
|
|
12361
|
+
const normalizedIdentifier = identifier?.toUpperCase();
|
|
12362
|
+
const matches = text.match(/\b[A-Z]+-\d+\b/g) ?? [];
|
|
12363
|
+
return matches.find((match) => match.toUpperCase() !== normalizedIdentifier) ?? null;
|
|
12364
|
+
}
|
|
12365
|
+
function explicitBoardInstructionForText(text) {
|
|
12366
|
+
return text.replace(/\s+/g, " ").trim().match(/(board should either .*?)(?:[.!?]|$)/i)?.[1] ?? null;
|
|
12367
|
+
}
|
|
12368
|
+
function semanticConfidenceForIntents(intents) {
|
|
12369
|
+
if (intents.has("resolution") || intents.has("share_with_board") || intents.has("confirmation") || intents.has("board_instruction")) {
|
|
12370
|
+
return "high";
|
|
12371
|
+
}
|
|
12372
|
+
if (intents.has("clarification")) return "medium";
|
|
12373
|
+
return null;
|
|
12374
|
+
}
|
|
12375
|
+
function relationHintsForIntents(intents, issueTarget) {
|
|
12376
|
+
const hints = [];
|
|
12377
|
+
const target = issueTarget ?? void 0;
|
|
12378
|
+
if (target) {
|
|
12379
|
+
hints.push({ kind: "same_issue", target });
|
|
12380
|
+
}
|
|
12381
|
+
if (intents.has("resolution")) {
|
|
12382
|
+
hints.push(target ? { kind: "resolves", target } : { kind: "resolves" });
|
|
12383
|
+
return hints;
|
|
12384
|
+
}
|
|
12385
|
+
if (intents.has("share_with_board") || intents.has("confirmation") || intents.has("board_instruction")) {
|
|
12386
|
+
hints.push(target ? { kind: "supersedes", target } : { kind: "supersedes" });
|
|
12387
|
+
return hints;
|
|
12388
|
+
}
|
|
12389
|
+
if (intents.has("clarification")) {
|
|
12390
|
+
hints.push(target ? { kind: "repeats", target } : { kind: "repeats" });
|
|
12391
|
+
}
|
|
12392
|
+
return hints;
|
|
12393
|
+
}
|
|
12394
|
+
function analyzeIssueTextSemantics(input) {
|
|
12395
|
+
const intents = detectIssueIntents(input.text);
|
|
12396
|
+
return {
|
|
12397
|
+
text: input.text,
|
|
12398
|
+
blockingTarget: blockingTargetForText(input.text, input.identifier),
|
|
12399
|
+
intents,
|
|
12400
|
+
explicitBoardInstruction: explicitBoardInstructionForText(input.text),
|
|
12401
|
+
semanticConfidence: semanticConfidenceForIntents(intents),
|
|
12402
|
+
relationHints: relationHintsForIntents(intents, input.issueTarget)
|
|
12403
|
+
};
|
|
12404
|
+
}
|
|
12339
12405
|
function resolveAgentOwner(text, directory) {
|
|
12340
12406
|
if (!directory || directory.agentNames.length === 0) return null;
|
|
12341
12407
|
const mentionMatch = text.match(/needed from\s+@([a-z0-9_-]+)/i) ?? text.match(/@([a-z0-9_-]+)/i);
|
|
@@ -12348,25 +12414,19 @@ function resolveAgentOwner(text, directory) {
|
|
|
12348
12414
|
}
|
|
12349
12415
|
function analyzeIssueIntents(issue, comment, directory) {
|
|
12350
12416
|
const text = issueText(issue, comment);
|
|
12351
|
-
const
|
|
12352
|
-
|
|
12353
|
-
|
|
12354
|
-
|
|
12417
|
+
const semanticAnalysis = analyzeIssueTextSemantics({
|
|
12418
|
+
text,
|
|
12419
|
+
identifier: issue.identifier,
|
|
12420
|
+
issueTarget: issueRelationTarget(issue.id)
|
|
12421
|
+
});
|
|
12355
12422
|
const agentOwner = resolveAgentOwner(text, directory);
|
|
12356
12423
|
const mentionMatch = text.match(/needed from\s+@([a-z0-9_-]+)/i) ?? text.match(/@([a-z0-9_-]+)/i);
|
|
12357
12424
|
const owner = agentOwner ?? (mentionMatch?.[1] ? titleCase(mentionMatch[1]) : /board should|board can/i.test(text) ? "Board" : /ceo agent|@ceo|\bceo\b/i.test(text) ? "CEO" : /requires human|waiting on human|human review/i.test(text) ? "Human" : /operator intervention|operator review/i.test(text) ? "Operator" : null);
|
|
12358
12425
|
const ownerKind = agentOwner ? "agent" : owner ? "human" : null;
|
|
12359
|
-
const identifier = issue.identifier?.toUpperCase();
|
|
12360
|
-
const matches = text.match(/\b[A-Z]+-\d+\b/g) ?? [];
|
|
12361
|
-
const blockingTarget = matches.find((match) => match.toUpperCase() !== identifier) ?? null;
|
|
12362
|
-
const explicitBoardInstruction = text.replace(/\s+/g, " ").trim().match(/(board should either .*?)(?:[.!?]|$)/i)?.[1] ?? null;
|
|
12363
12426
|
return {
|
|
12364
|
-
|
|
12427
|
+
...semanticAnalysis,
|
|
12365
12428
|
owner,
|
|
12366
|
-
ownerKind
|
|
12367
|
-
blockingTarget,
|
|
12368
|
-
intents,
|
|
12369
|
-
explicitBoardInstruction
|
|
12429
|
+
ownerKind
|
|
12370
12430
|
};
|
|
12371
12431
|
}
|
|
12372
12432
|
function hasIntent(analysis, key) {
|
|
@@ -12376,8 +12436,28 @@ function ownerPhrase(value, ownerKind) {
|
|
|
12376
12436
|
if (ownerKind === "agent") return value;
|
|
12377
12437
|
return actorPhrase(value);
|
|
12378
12438
|
}
|
|
12379
|
-
function
|
|
12439
|
+
function latestIssueDocument(documents) {
|
|
12440
|
+
if (documents.length === 0) return null;
|
|
12441
|
+
return [...documents].sort(
|
|
12442
|
+
(left, right) => toTimestamp(right.updatedAt) - toTimestamp(left.updatedAt)
|
|
12443
|
+
)[0] ?? null;
|
|
12444
|
+
}
|
|
12445
|
+
function analyzeIssueDocuments(documents, comment, analysis) {
|
|
12446
|
+
const latest = latestIssueDocument(documents);
|
|
12447
|
+
const latestUpdatedAt = toIsoString(latest?.updatedAt);
|
|
12448
|
+
const commentUpdatedAt = comment?.updatedAt ?? null;
|
|
12449
|
+
return {
|
|
12450
|
+
hasDocuments: documents.length > 0,
|
|
12451
|
+
latestDocumentTitle: latest?.title ?? null,
|
|
12452
|
+
latestDocumentUpdatedAt: latestUpdatedAt,
|
|
12453
|
+
resolvesArtifactRequest: hasIntent(analysis, "share_with_board") && !!latest && (!commentUpdatedAt || toTimestamp(latestUpdatedAt) >= toTimestamp(commentUpdatedAt))
|
|
12454
|
+
};
|
|
12455
|
+
}
|
|
12456
|
+
function issueRecommendedMove(issue, analysis, documentSignal) {
|
|
12380
12457
|
const normalized = analysis.text.replace(/\s+/g, " ").trim();
|
|
12458
|
+
if (documentSignal?.resolvesArtifactRequest) {
|
|
12459
|
+
return "Monitor the review now that the memo is attached.";
|
|
12460
|
+
}
|
|
12381
12461
|
if (hasIntent(analysis, "resolution")) {
|
|
12382
12462
|
return "Monitor execution and confirm the team resumes work.";
|
|
12383
12463
|
}
|
|
@@ -12402,7 +12482,10 @@ function issueRecommendedMove(issue, analysis) {
|
|
|
12402
12482
|
}
|
|
12403
12483
|
return null;
|
|
12404
12484
|
}
|
|
12405
|
-
function issueHeadline(issue, analysis) {
|
|
12485
|
+
function issueHeadline(issue, analysis, documentSignal) {
|
|
12486
|
+
if (documentSignal?.resolvesArtifactRequest) {
|
|
12487
|
+
return "The requested memo appears attached, so review should be able to continue.";
|
|
12488
|
+
}
|
|
12406
12489
|
if (hasIntent(analysis, "resolution")) {
|
|
12407
12490
|
return "Latest operator guidance appears to unblock this issue.";
|
|
12408
12491
|
}
|
|
@@ -12426,7 +12509,13 @@ function issueHeadline(issue, analysis) {
|
|
|
12426
12509
|
if (issue.description) return truncate(issue.description);
|
|
12427
12510
|
return "This issue has new operator-relevant activity.";
|
|
12428
12511
|
}
|
|
12429
|
-
function issueWhyNow(issue, analysis) {
|
|
12512
|
+
function issueWhyNow(issue, analysis, documentSignal) {
|
|
12513
|
+
if (documentSignal?.resolvesArtifactRequest) {
|
|
12514
|
+
return {
|
|
12515
|
+
whyNow: documentSignal.latestDocumentTitle ? `${documentSignal.latestDocumentTitle} was attached after the request, so the missing artifact appears resolved.` : "A review document was attached after the request, so the missing artifact appears resolved.",
|
|
12516
|
+
factors: ["document attached", "review can proceed"]
|
|
12517
|
+
};
|
|
12518
|
+
}
|
|
12430
12519
|
if (hasIntent(analysis, "resolution")) {
|
|
12431
12520
|
return {
|
|
12432
12521
|
whyNow: "The latest comment appears to resolve the blocker; monitor execution rather than intervening.",
|
|
@@ -12467,12 +12556,19 @@ function issueWhyNow(issue, analysis) {
|
|
|
12467
12556
|
|
|
12468
12557
|
// src/aperture/reconciliation.ts
|
|
12469
12558
|
var COMMENT_LOOKUP_CONCURRENCY = 6;
|
|
12470
|
-
function
|
|
12559
|
+
function semanticMetadata(confidence, relationHints) {
|
|
12560
|
+
const semantic = {
|
|
12561
|
+
...confidence ? { confidence } : {},
|
|
12562
|
+
...relationHints.length > 0 ? { relationHints } : {}
|
|
12563
|
+
};
|
|
12564
|
+
return Object.keys(semantic).length > 0 ? semantic : void 0;
|
|
12565
|
+
}
|
|
12566
|
+
function toIsoString2(value) {
|
|
12471
12567
|
if (!value) return void 0;
|
|
12472
12568
|
if (typeof value === "string") return value;
|
|
12473
12569
|
return value.toISOString();
|
|
12474
12570
|
}
|
|
12475
|
-
function
|
|
12571
|
+
function toTimestamp2(value) {
|
|
12476
12572
|
if (!value) return 0;
|
|
12477
12573
|
if (value instanceof Date) return value.getTime();
|
|
12478
12574
|
const timestamp = Date.parse(value);
|
|
@@ -12489,20 +12585,12 @@ function acknowledgeResponseSpec() {
|
|
|
12489
12585
|
function issueTitle(issue) {
|
|
12490
12586
|
return issue.identifier ? `${issue.identifier} ${issue.title}` : issue.title;
|
|
12491
12587
|
}
|
|
12492
|
-
function recommendedMove(issue, comment, directory) {
|
|
12493
|
-
const analysis = analyzeIssueIntents(issue, comment, directory);
|
|
12494
|
-
return issueRecommendedMove(issue, analysis);
|
|
12495
|
-
}
|
|
12496
|
-
function issueHeadline2(issue, comment, directory) {
|
|
12497
|
-
const analysis = analyzeIssueIntents(issue, comment, directory);
|
|
12498
|
-
return issueHeadline(issue, analysis);
|
|
12499
|
-
}
|
|
12500
12588
|
function latestComment(comments) {
|
|
12501
12589
|
if (comments.length === 0) return null;
|
|
12502
12590
|
const newest = [...comments].sort(
|
|
12503
|
-
(left, right) =>
|
|
12591
|
+
(left, right) => toTimestamp2(right.updatedAt) - toTimestamp2(left.updatedAt)
|
|
12504
12592
|
)[0];
|
|
12505
|
-
const updatedAt =
|
|
12593
|
+
const updatedAt = toIsoString2(newest.updatedAt) ?? toIsoString2(newest.createdAt) ?? (/* @__PURE__ */ new Date(0)).toISOString();
|
|
12506
12594
|
return {
|
|
12507
12595
|
body: truncate(newest.body),
|
|
12508
12596
|
updatedAt
|
|
@@ -12511,37 +12599,38 @@ function latestComment(comments) {
|
|
|
12511
12599
|
function priorityConsequence(issue) {
|
|
12512
12600
|
return issue.priority === "critical" || issue.priority === "high" ? "high" : "medium";
|
|
12513
12601
|
}
|
|
12514
|
-
function issueLane(issue, comment,
|
|
12515
|
-
|
|
12516
|
-
|
|
12517
|
-
return comment || issue.isUnreadForMe ? "ambient" : null;
|
|
12602
|
+
function issueLane(issue, comment, evidence) {
|
|
12603
|
+
if ((issue.status === "blocked" || issue.status === "in_review") && (hasIntent(evidence.analysis, "resolution") || evidence.documentSignal.resolvesArtifactRequest)) {
|
|
12604
|
+
return comment || issue.isUnreadForMe || evidence.documentSignal.hasDocuments ? "ambient" : null;
|
|
12518
12605
|
}
|
|
12519
12606
|
if (issue.status === "blocked") return issue.priority === "critical" ? "active" : "queued";
|
|
12520
12607
|
if (issue.status === "in_review") return "queued";
|
|
12521
12608
|
if (comment || issue.isUnreadForMe) return "ambient";
|
|
12522
12609
|
return null;
|
|
12523
12610
|
}
|
|
12524
|
-
function issueSummary(issue,
|
|
12525
|
-
return
|
|
12611
|
+
function issueSummary(issue, evidence) {
|
|
12612
|
+
return issueHeadline(issue, evidence.analysis, evidence.documentSignal);
|
|
12526
12613
|
}
|
|
12527
|
-
function issueProvenance(issue,
|
|
12528
|
-
|
|
12529
|
-
return issueWhyNow(issue, analysis);
|
|
12614
|
+
function issueProvenance(issue, evidence) {
|
|
12615
|
+
return issueWhyNow(issue, evidence.analysis, evidence.documentSignal);
|
|
12530
12616
|
}
|
|
12531
|
-
function issueAttentionTimestamp(issue, comment) {
|
|
12532
|
-
const commentSignals = [comment?.updatedAt,
|
|
12617
|
+
function issueAttentionTimestamp(issue, comment, documentSignal) {
|
|
12618
|
+
const commentSignals = [comment?.updatedAt, toIsoString2(issue.lastExternalCommentAt), documentSignal.latestDocumentUpdatedAt].filter((value) => !!value).sort();
|
|
12533
12619
|
if (commentSignals.length > 0) return commentSignals.at(-1);
|
|
12534
|
-
return
|
|
12620
|
+
return toIsoString2(issue.updatedAt) ?? toIsoString2(issue.createdAt) ?? (/* @__PURE__ */ new Date(0)).toISOString();
|
|
12535
12621
|
}
|
|
12536
|
-
function issueFrame(issue, comment, directory) {
|
|
12537
|
-
const lane = issueLane(issue, comment, directory);
|
|
12538
|
-
if (!lane) return null;
|
|
12539
|
-
const updatedAt = issueAttentionTimestamp(issue, comment);
|
|
12540
|
-
const provenance = issueProvenance(issue, comment, directory);
|
|
12622
|
+
function issueFrame(issue, comment, documents, directory) {
|
|
12541
12623
|
const analysis = analyzeIssueIntents(issue, comment, directory);
|
|
12542
|
-
const
|
|
12543
|
-
const
|
|
12624
|
+
const documentSignal = analyzeIssueDocuments(documents, comment, analysis);
|
|
12625
|
+
const evidence = { analysis, documentSignal };
|
|
12626
|
+
const lane = issueLane(issue, comment, evidence);
|
|
12627
|
+
if (!lane) return null;
|
|
12628
|
+
const updatedAt = issueAttentionTimestamp(issue, comment, documentSignal);
|
|
12629
|
+
const provenance = issueProvenance(issue, evidence);
|
|
12630
|
+
const owner = hasIntent(analysis, "resolution") || documentSignal.resolvesArtifactRequest ? null : analysis.owner;
|
|
12631
|
+
const move = issueRecommendedMove(issue, analysis, documentSignal);
|
|
12544
12632
|
const target = analysis.blockingTarget;
|
|
12633
|
+
const semantic = semanticMetadata(analysis.semanticConfidence, analysis.relationHints);
|
|
12545
12634
|
return {
|
|
12546
12635
|
lane,
|
|
12547
12636
|
frame: {
|
|
@@ -12558,7 +12647,7 @@ function issueFrame(issue, comment, directory) {
|
|
|
12558
12647
|
tone: issue.status === "blocked" ? "focused" : lane === "ambient" ? "ambient" : "focused",
|
|
12559
12648
|
consequence: issue.status === "blocked" ? priorityConsequence(issue) : lane === "ambient" ? "low" : "medium",
|
|
12560
12649
|
title: issueTitle(issue),
|
|
12561
|
-
summary: issueSummary(issue,
|
|
12650
|
+
summary: issueSummary(issue, evidence),
|
|
12562
12651
|
context: {
|
|
12563
12652
|
items: [
|
|
12564
12653
|
...owner ? [needsActionFromItem(owner)] : [],
|
|
@@ -12572,7 +12661,7 @@ function issueFrame(issue, comment, directory) {
|
|
|
12572
12661
|
responseSpec: acknowledgeResponseSpec(),
|
|
12573
12662
|
provenance,
|
|
12574
12663
|
timing: {
|
|
12575
|
-
createdAt:
|
|
12664
|
+
createdAt: toIsoString2(issue.createdAt) ?? updatedAt,
|
|
12576
12665
|
updatedAt
|
|
12577
12666
|
},
|
|
12578
12667
|
metadata: {
|
|
@@ -12580,7 +12669,11 @@ function issueFrame(issue, comment, directory) {
|
|
|
12580
12669
|
issueStatus: issue.status,
|
|
12581
12670
|
issuePriority: issue.priority,
|
|
12582
12671
|
liveReconciled: true,
|
|
12583
|
-
activityPath: comment ? "activity" : void 0
|
|
12672
|
+
activityPath: comment ? "activity" : void 0,
|
|
12673
|
+
attention: {
|
|
12674
|
+
rationale: provenance.factors ?? []
|
|
12675
|
+
},
|
|
12676
|
+
...semantic ? { semantic } : {}
|
|
12584
12677
|
}
|
|
12585
12678
|
}
|
|
12586
12679
|
};
|
|
@@ -12601,7 +12694,7 @@ function agentSummary(agent) {
|
|
|
12601
12694
|
function agentFrame(agent) {
|
|
12602
12695
|
const lane = agentLane(agent);
|
|
12603
12696
|
if (!lane) return null;
|
|
12604
|
-
const updatedAt =
|
|
12697
|
+
const updatedAt = toIsoString2(agent.updatedAt) ?? toIsoString2(agent.createdAt) ?? (/* @__PURE__ */ new Date(0)).toISOString();
|
|
12605
12698
|
const provenance = agentProvenance(agent);
|
|
12606
12699
|
return {
|
|
12607
12700
|
lane,
|
|
@@ -12630,7 +12723,7 @@ function agentFrame(agent) {
|
|
|
12630
12723
|
responseSpec: acknowledgeResponseSpec(),
|
|
12631
12724
|
provenance,
|
|
12632
12725
|
timing: {
|
|
12633
|
-
createdAt:
|
|
12726
|
+
createdAt: toIsoString2(agent.createdAt) ?? updatedAt,
|
|
12634
12727
|
updatedAt
|
|
12635
12728
|
},
|
|
12636
12729
|
metadata: {
|
|
@@ -12638,7 +12731,13 @@ function agentFrame(agent) {
|
|
|
12638
12731
|
agentStatus: agent.status,
|
|
12639
12732
|
pauseReason: agent.pauseReason,
|
|
12640
12733
|
liveReconciled: true,
|
|
12641
|
-
activityPath: agent.status === "error" ? "activity" : void 0
|
|
12734
|
+
activityPath: agent.status === "error" ? "activity" : void 0,
|
|
12735
|
+
attention: {
|
|
12736
|
+
rationale: provenance.factors ?? []
|
|
12737
|
+
},
|
|
12738
|
+
semantic: {
|
|
12739
|
+
confidence: "high"
|
|
12740
|
+
}
|
|
12642
12741
|
}
|
|
12643
12742
|
}
|
|
12644
12743
|
};
|
|
@@ -12655,6 +12754,17 @@ async function safeListComments(ctx, issue) {
|
|
|
12655
12754
|
return null;
|
|
12656
12755
|
}
|
|
12657
12756
|
}
|
|
12757
|
+
async function safeListDocuments(ctx, issue) {
|
|
12758
|
+
try {
|
|
12759
|
+
return await ctx.issues.documents.list(issue.id, issue.companyId);
|
|
12760
|
+
} catch (error) {
|
|
12761
|
+
ctx.logger.warn("Failed to load issue documents during Aperture reconciliation", {
|
|
12762
|
+
issueId: issue.id,
|
|
12763
|
+
error: error instanceof Error ? error.message : String(error)
|
|
12764
|
+
});
|
|
12765
|
+
return [];
|
|
12766
|
+
}
|
|
12767
|
+
}
|
|
12658
12768
|
async function mapWithConcurrency(items, limit, mapper) {
|
|
12659
12769
|
if (items.length === 0) return [];
|
|
12660
12770
|
const results = new Array(items.length);
|
|
@@ -12676,16 +12786,23 @@ async function loadIssueCandidates(ctx, companyId) {
|
|
|
12676
12786
|
ctx.issues.list({ companyId, status: "in_review", limit: 25 })
|
|
12677
12787
|
]);
|
|
12678
12788
|
const issues = [...blocked, ...inReview];
|
|
12679
|
-
const comments = await
|
|
12680
|
-
|
|
12681
|
-
|
|
12682
|
-
|
|
12683
|
-
|
|
12684
|
-
|
|
12789
|
+
const [comments, documents, allAgents] = await Promise.all([
|
|
12790
|
+
mapWithConcurrency(
|
|
12791
|
+
issues,
|
|
12792
|
+
COMMENT_LOOKUP_CONCURRENCY,
|
|
12793
|
+
(issue) => safeListComments(ctx, issue)
|
|
12794
|
+
),
|
|
12795
|
+
mapWithConcurrency(
|
|
12796
|
+
issues,
|
|
12797
|
+
COMMENT_LOOKUP_CONCURRENCY,
|
|
12798
|
+
(issue) => safeListDocuments(ctx, issue)
|
|
12799
|
+
),
|
|
12800
|
+
ctx.agents.list({ companyId, limit: 100 })
|
|
12801
|
+
]);
|
|
12685
12802
|
const directory = {
|
|
12686
12803
|
agentNames: allAgents.map((agent) => agent.name).filter((name) => typeof name === "string" && name.trim().length > 0)
|
|
12687
12804
|
};
|
|
12688
|
-
return issues.map((issue, index) => issueFrame(issue, comments[index] ?? null, directory)).filter((candidate) => candidate !== null);
|
|
12805
|
+
return issues.map((issue, index) => issueFrame(issue, comments[index] ?? null, documents[index] ?? [], directory)).filter((candidate) => candidate !== null);
|
|
12689
12806
|
}
|
|
12690
12807
|
async function loadAgentCandidates(ctx, companyId) {
|
|
12691
12808
|
const [pendingApproval, errored, paused] = await Promise.all([
|
|
@@ -13457,9 +13574,6 @@ function issueDisplayTitle(event, fallback) {
|
|
|
13457
13574
|
if (issueTitle2) return issueTitle2;
|
|
13458
13575
|
return fallback;
|
|
13459
13576
|
}
|
|
13460
|
-
function issueRelationTarget(issueId) {
|
|
13461
|
-
return `issue:${issueId}`;
|
|
13462
|
-
}
|
|
13463
13577
|
function relationHintsForIssueTargets(issueIds) {
|
|
13464
13578
|
if (!issueIds || issueIds.length === 0) return void 0;
|
|
13465
13579
|
return {
|
|
@@ -13469,12 +13583,29 @@ function relationHintsForIssueTargets(issueIds) {
|
|
|
13469
13583
|
}))
|
|
13470
13584
|
};
|
|
13471
13585
|
}
|
|
13586
|
+
function issueSemanticText(payload) {
|
|
13587
|
+
return [
|
|
13588
|
+
readPayloadString(payload, "bodySnippet"),
|
|
13589
|
+
readPayloadString(payload, "summary"),
|
|
13590
|
+
readPayloadString(payload, "description"),
|
|
13591
|
+
readPayloadString(payload, "title"),
|
|
13592
|
+
readPayloadString(payload, "issueTitle")
|
|
13593
|
+
].filter((value) => !!value).join("\n");
|
|
13594
|
+
}
|
|
13472
13595
|
function issueSemanticHints(event) {
|
|
13473
|
-
|
|
13596
|
+
if (event.entityType !== "issue" || !event.entityId) return void 0;
|
|
13597
|
+
const semanticAnalysis = analyzeIssueTextSemantics({
|
|
13598
|
+
text: issueSemanticText(event.payload),
|
|
13599
|
+
identifier: readPayloadString(event.payload, "identifier"),
|
|
13600
|
+
issueTarget: issueRelationTarget(event.entityId)
|
|
13601
|
+
});
|
|
13602
|
+
return {
|
|
13603
|
+
...semanticAnalysis.semanticConfidence ? { confidence: semanticAnalysis.semanticConfidence } : {},
|
|
13604
|
+
...semanticAnalysis.relationHints.length > 0 ? { relationHints: semanticAnalysis.relationHints } : {}
|
|
13605
|
+
};
|
|
13474
13606
|
}
|
|
13475
13607
|
function approvalSemanticHints(budgetOverride, linkedIssueIds) {
|
|
13476
13608
|
return {
|
|
13477
|
-
confidence: "high",
|
|
13478
13609
|
whyNow: approvalBlockingWhyNow(budgetOverride),
|
|
13479
13610
|
factors: budgetOverride ? ["budget stop", "approval", "operator decision"] : ["approval", "operator decision"],
|
|
13480
13611
|
...relationHintsForIssueTargets(linkedIssueIds) ?? {}
|
|
@@ -13482,7 +13613,6 @@ function approvalSemanticHints(budgetOverride, linkedIssueIds) {
|
|
|
13482
13613
|
}
|
|
13483
13614
|
function pendingApprovalSemanticHints() {
|
|
13484
13615
|
return {
|
|
13485
|
-
confidence: "high",
|
|
13486
13616
|
whyNow: pendingApprovalEventWhyNow(),
|
|
13487
13617
|
factors: ["pending approval"]
|
|
13488
13618
|
};
|
|
@@ -13490,7 +13620,6 @@ function pendingApprovalSemanticHints() {
|
|
|
13490
13620
|
function runFailureSemanticHints() {
|
|
13491
13621
|
return {
|
|
13492
13622
|
intentFrame: "failure",
|
|
13493
|
-
confidence: "high",
|
|
13494
13623
|
whyNow: runFailedWhyNow(),
|
|
13495
13624
|
factors: ["run failed", "operator review"]
|
|
13496
13625
|
};
|
package/package.json
CHANGED