@tomismeta/paperclip-aperture 0.2.1 → 0.2.2
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 +10 -2
- package/dist/manifest.js +2 -1
- package/dist/worker.js +176 -65
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -68,6 +68,8 @@ and agents attention now? actually sees to th
|
|
|
68
68
|
- issue-aware operator language such as `review required`, `blocked`, and targeted recommended moves
|
|
69
69
|
- agent-aware routing that distinguishes known company agents from human/operator roles when issue text references them
|
|
70
70
|
- a plugin-local deterministic semantic mapping layer that interprets Paperclip issue, approval, and agent signals before publishing them into Aperture Core
|
|
71
|
+
- richer semantic continuity hints on mapped issue events, including `supersedes` and `resolves` relationships where Paperclip-specific intent is clear
|
|
72
|
+
- 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
73
|
- dynamic re-stacking so items can move between `now`, `next`, and `ambient` as new evidence arrives
|
|
72
74
|
- inline issue commenting from the Focus surface when a Paperclip issue supports written response
|
|
73
75
|
- durable acknowledge/suppression behavior backed by plugin state and ledger replay
|
|
@@ -104,14 +106,19 @@ Before releasing, run:
|
|
|
104
106
|
pnpm release:check
|
|
105
107
|
```
|
|
106
108
|
|
|
107
|
-
For a live local Paperclip smoke test:
|
|
109
|
+
For a live local Paperclip smoke test, start Paperclip first:
|
|
108
110
|
|
|
109
111
|
```bash
|
|
110
112
|
pnpm build
|
|
113
|
+
npx paperclipai run -i default
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Then, in a second terminal:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
111
119
|
npx paperclipai context set --api-base http://localhost:3100
|
|
112
120
|
npx paperclipai plugin uninstall tomismeta.paperclip-aperture --force
|
|
113
121
|
npx paperclipai plugin install --local .
|
|
114
|
-
npx paperclipai run -i default
|
|
115
122
|
```
|
|
116
123
|
|
|
117
124
|
Then open `http://127.0.0.1:3100/APE/aperture` and verify:
|
|
@@ -121,6 +128,7 @@ Then open `http://127.0.0.1:3100/APE/aperture` and verify:
|
|
|
121
128
|
- issue comments post successfully from Focus
|
|
122
129
|
- approval actions update Focus correctly
|
|
123
130
|
- resolved blocker comments downgrade stale `Now` items
|
|
131
|
+
- attached issue documents downgrade stale `share the memo/spec` review blockers into monitor-only follow-up
|
|
124
132
|
|
|
125
133
|
## Links
|
|
126
134
|
|
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.2.
|
|
4
|
+
version: "0.2.2",
|
|
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/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,12 @@ function issueWhyNow(issue, analysis) {
|
|
|
12467
12556
|
|
|
12468
12557
|
// src/aperture/reconciliation.ts
|
|
12469
12558
|
var COMMENT_LOOKUP_CONCURRENCY = 6;
|
|
12470
|
-
function
|
|
12559
|
+
function toIsoString2(value) {
|
|
12471
12560
|
if (!value) return void 0;
|
|
12472
12561
|
if (typeof value === "string") return value;
|
|
12473
12562
|
return value.toISOString();
|
|
12474
12563
|
}
|
|
12475
|
-
function
|
|
12564
|
+
function toTimestamp2(value) {
|
|
12476
12565
|
if (!value) return 0;
|
|
12477
12566
|
if (value instanceof Date) return value.getTime();
|
|
12478
12567
|
const timestamp = Date.parse(value);
|
|
@@ -12489,20 +12578,12 @@ function acknowledgeResponseSpec() {
|
|
|
12489
12578
|
function issueTitle(issue) {
|
|
12490
12579
|
return issue.identifier ? `${issue.identifier} ${issue.title}` : issue.title;
|
|
12491
12580
|
}
|
|
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
12581
|
function latestComment(comments) {
|
|
12501
12582
|
if (comments.length === 0) return null;
|
|
12502
12583
|
const newest = [...comments].sort(
|
|
12503
|
-
(left, right) =>
|
|
12584
|
+
(left, right) => toTimestamp2(right.updatedAt) - toTimestamp2(left.updatedAt)
|
|
12504
12585
|
)[0];
|
|
12505
|
-
const updatedAt =
|
|
12586
|
+
const updatedAt = toIsoString2(newest.updatedAt) ?? toIsoString2(newest.createdAt) ?? (/* @__PURE__ */ new Date(0)).toISOString();
|
|
12506
12587
|
return {
|
|
12507
12588
|
body: truncate(newest.body),
|
|
12508
12589
|
updatedAt
|
|
@@ -12511,36 +12592,36 @@ function latestComment(comments) {
|
|
|
12511
12592
|
function priorityConsequence(issue) {
|
|
12512
12593
|
return issue.priority === "critical" || issue.priority === "high" ? "high" : "medium";
|
|
12513
12594
|
}
|
|
12514
|
-
function issueLane(issue, comment,
|
|
12515
|
-
|
|
12516
|
-
|
|
12517
|
-
return comment || issue.isUnreadForMe ? "ambient" : null;
|
|
12595
|
+
function issueLane(issue, comment, evidence) {
|
|
12596
|
+
if ((issue.status === "blocked" || issue.status === "in_review") && (hasIntent(evidence.analysis, "resolution") || evidence.documentSignal.resolvesArtifactRequest)) {
|
|
12597
|
+
return comment || issue.isUnreadForMe || evidence.documentSignal.hasDocuments ? "ambient" : null;
|
|
12518
12598
|
}
|
|
12519
12599
|
if (issue.status === "blocked") return issue.priority === "critical" ? "active" : "queued";
|
|
12520
12600
|
if (issue.status === "in_review") return "queued";
|
|
12521
12601
|
if (comment || issue.isUnreadForMe) return "ambient";
|
|
12522
12602
|
return null;
|
|
12523
12603
|
}
|
|
12524
|
-
function issueSummary(issue,
|
|
12525
|
-
return
|
|
12604
|
+
function issueSummary(issue, evidence) {
|
|
12605
|
+
return issueHeadline(issue, evidence.analysis, evidence.documentSignal);
|
|
12526
12606
|
}
|
|
12527
|
-
function issueProvenance(issue,
|
|
12528
|
-
|
|
12529
|
-
return issueWhyNow(issue, analysis);
|
|
12607
|
+
function issueProvenance(issue, evidence) {
|
|
12608
|
+
return issueWhyNow(issue, evidence.analysis, evidence.documentSignal);
|
|
12530
12609
|
}
|
|
12531
|
-
function issueAttentionTimestamp(issue, comment) {
|
|
12532
|
-
const commentSignals = [comment?.updatedAt,
|
|
12610
|
+
function issueAttentionTimestamp(issue, comment, documentSignal) {
|
|
12611
|
+
const commentSignals = [comment?.updatedAt, toIsoString2(issue.lastExternalCommentAt), documentSignal.latestDocumentUpdatedAt].filter((value) => !!value).sort();
|
|
12533
12612
|
if (commentSignals.length > 0) return commentSignals.at(-1);
|
|
12534
|
-
return
|
|
12613
|
+
return toIsoString2(issue.updatedAt) ?? toIsoString2(issue.createdAt) ?? (/* @__PURE__ */ new Date(0)).toISOString();
|
|
12535
12614
|
}
|
|
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);
|
|
12615
|
+
function issueFrame(issue, comment, documents, directory) {
|
|
12541
12616
|
const analysis = analyzeIssueIntents(issue, comment, directory);
|
|
12542
|
-
const
|
|
12543
|
-
const
|
|
12617
|
+
const documentSignal = analyzeIssueDocuments(documents, comment, analysis);
|
|
12618
|
+
const evidence = { analysis, documentSignal };
|
|
12619
|
+
const lane = issueLane(issue, comment, evidence);
|
|
12620
|
+
if (!lane) return null;
|
|
12621
|
+
const updatedAt = issueAttentionTimestamp(issue, comment, documentSignal);
|
|
12622
|
+
const provenance = issueProvenance(issue, evidence);
|
|
12623
|
+
const owner = hasIntent(analysis, "resolution") || documentSignal.resolvesArtifactRequest ? null : analysis.owner;
|
|
12624
|
+
const move = issueRecommendedMove(issue, analysis, documentSignal);
|
|
12544
12625
|
const target = analysis.blockingTarget;
|
|
12545
12626
|
return {
|
|
12546
12627
|
lane,
|
|
@@ -12558,7 +12639,7 @@ function issueFrame(issue, comment, directory) {
|
|
|
12558
12639
|
tone: issue.status === "blocked" ? "focused" : lane === "ambient" ? "ambient" : "focused",
|
|
12559
12640
|
consequence: issue.status === "blocked" ? priorityConsequence(issue) : lane === "ambient" ? "low" : "medium",
|
|
12560
12641
|
title: issueTitle(issue),
|
|
12561
|
-
summary: issueSummary(issue,
|
|
12642
|
+
summary: issueSummary(issue, evidence),
|
|
12562
12643
|
context: {
|
|
12563
12644
|
items: [
|
|
12564
12645
|
...owner ? [needsActionFromItem(owner)] : [],
|
|
@@ -12572,7 +12653,7 @@ function issueFrame(issue, comment, directory) {
|
|
|
12572
12653
|
responseSpec: acknowledgeResponseSpec(),
|
|
12573
12654
|
provenance,
|
|
12574
12655
|
timing: {
|
|
12575
|
-
createdAt:
|
|
12656
|
+
createdAt: toIsoString2(issue.createdAt) ?? updatedAt,
|
|
12576
12657
|
updatedAt
|
|
12577
12658
|
},
|
|
12578
12659
|
metadata: {
|
|
@@ -12601,7 +12682,7 @@ function agentSummary(agent) {
|
|
|
12601
12682
|
function agentFrame(agent) {
|
|
12602
12683
|
const lane = agentLane(agent);
|
|
12603
12684
|
if (!lane) return null;
|
|
12604
|
-
const updatedAt =
|
|
12685
|
+
const updatedAt = toIsoString2(agent.updatedAt) ?? toIsoString2(agent.createdAt) ?? (/* @__PURE__ */ new Date(0)).toISOString();
|
|
12605
12686
|
const provenance = agentProvenance(agent);
|
|
12606
12687
|
return {
|
|
12607
12688
|
lane,
|
|
@@ -12630,7 +12711,7 @@ function agentFrame(agent) {
|
|
|
12630
12711
|
responseSpec: acknowledgeResponseSpec(),
|
|
12631
12712
|
provenance,
|
|
12632
12713
|
timing: {
|
|
12633
|
-
createdAt:
|
|
12714
|
+
createdAt: toIsoString2(agent.createdAt) ?? updatedAt,
|
|
12634
12715
|
updatedAt
|
|
12635
12716
|
},
|
|
12636
12717
|
metadata: {
|
|
@@ -12655,6 +12736,17 @@ async function safeListComments(ctx, issue) {
|
|
|
12655
12736
|
return null;
|
|
12656
12737
|
}
|
|
12657
12738
|
}
|
|
12739
|
+
async function safeListDocuments(ctx, issue) {
|
|
12740
|
+
try {
|
|
12741
|
+
return await ctx.issues.documents.list(issue.id, issue.companyId);
|
|
12742
|
+
} catch (error) {
|
|
12743
|
+
ctx.logger.warn("Failed to load issue documents during Aperture reconciliation", {
|
|
12744
|
+
issueId: issue.id,
|
|
12745
|
+
error: error instanceof Error ? error.message : String(error)
|
|
12746
|
+
});
|
|
12747
|
+
return [];
|
|
12748
|
+
}
|
|
12749
|
+
}
|
|
12658
12750
|
async function mapWithConcurrency(items, limit, mapper) {
|
|
12659
12751
|
if (items.length === 0) return [];
|
|
12660
12752
|
const results = new Array(items.length);
|
|
@@ -12676,16 +12768,23 @@ async function loadIssueCandidates(ctx, companyId) {
|
|
|
12676
12768
|
ctx.issues.list({ companyId, status: "in_review", limit: 25 })
|
|
12677
12769
|
]);
|
|
12678
12770
|
const issues = [...blocked, ...inReview];
|
|
12679
|
-
const comments = await
|
|
12680
|
-
|
|
12681
|
-
|
|
12682
|
-
|
|
12683
|
-
|
|
12684
|
-
|
|
12771
|
+
const [comments, documents, allAgents] = await Promise.all([
|
|
12772
|
+
mapWithConcurrency(
|
|
12773
|
+
issues,
|
|
12774
|
+
COMMENT_LOOKUP_CONCURRENCY,
|
|
12775
|
+
(issue) => safeListComments(ctx, issue)
|
|
12776
|
+
),
|
|
12777
|
+
mapWithConcurrency(
|
|
12778
|
+
issues,
|
|
12779
|
+
COMMENT_LOOKUP_CONCURRENCY,
|
|
12780
|
+
(issue) => safeListDocuments(ctx, issue)
|
|
12781
|
+
),
|
|
12782
|
+
ctx.agents.list({ companyId, limit: 100 })
|
|
12783
|
+
]);
|
|
12685
12784
|
const directory = {
|
|
12686
12785
|
agentNames: allAgents.map((agent) => agent.name).filter((name) => typeof name === "string" && name.trim().length > 0)
|
|
12687
12786
|
};
|
|
12688
|
-
return issues.map((issue, index) => issueFrame(issue, comments[index] ?? null, directory)).filter((candidate) => candidate !== null);
|
|
12787
|
+
return issues.map((issue, index) => issueFrame(issue, comments[index] ?? null, documents[index] ?? [], directory)).filter((candidate) => candidate !== null);
|
|
12689
12788
|
}
|
|
12690
12789
|
async function loadAgentCandidates(ctx, companyId) {
|
|
12691
12790
|
const [pendingApproval, errored, paused] = await Promise.all([
|
|
@@ -13457,9 +13556,6 @@ function issueDisplayTitle(event, fallback) {
|
|
|
13457
13556
|
if (issueTitle2) return issueTitle2;
|
|
13458
13557
|
return fallback;
|
|
13459
13558
|
}
|
|
13460
|
-
function issueRelationTarget(issueId) {
|
|
13461
|
-
return `issue:${issueId}`;
|
|
13462
|
-
}
|
|
13463
13559
|
function relationHintsForIssueTargets(issueIds) {
|
|
13464
13560
|
if (!issueIds || issueIds.length === 0) return void 0;
|
|
13465
13561
|
return {
|
|
@@ -13469,12 +13565,29 @@ function relationHintsForIssueTargets(issueIds) {
|
|
|
13469
13565
|
}))
|
|
13470
13566
|
};
|
|
13471
13567
|
}
|
|
13568
|
+
function issueSemanticText(payload) {
|
|
13569
|
+
return [
|
|
13570
|
+
readPayloadString(payload, "bodySnippet"),
|
|
13571
|
+
readPayloadString(payload, "summary"),
|
|
13572
|
+
readPayloadString(payload, "description"),
|
|
13573
|
+
readPayloadString(payload, "title"),
|
|
13574
|
+
readPayloadString(payload, "issueTitle")
|
|
13575
|
+
].filter((value) => !!value).join("\n");
|
|
13576
|
+
}
|
|
13472
13577
|
function issueSemanticHints(event) {
|
|
13473
|
-
|
|
13578
|
+
if (event.entityType !== "issue" || !event.entityId) return void 0;
|
|
13579
|
+
const semanticAnalysis = analyzeIssueTextSemantics({
|
|
13580
|
+
text: issueSemanticText(event.payload),
|
|
13581
|
+
identifier: readPayloadString(event.payload, "identifier"),
|
|
13582
|
+
issueTarget: issueRelationTarget(event.entityId)
|
|
13583
|
+
});
|
|
13584
|
+
return {
|
|
13585
|
+
...semanticAnalysis.semanticConfidence ? { confidence: semanticAnalysis.semanticConfidence } : {},
|
|
13586
|
+
...semanticAnalysis.relationHints.length > 0 ? { relationHints: semanticAnalysis.relationHints } : {}
|
|
13587
|
+
};
|
|
13474
13588
|
}
|
|
13475
13589
|
function approvalSemanticHints(budgetOverride, linkedIssueIds) {
|
|
13476
13590
|
return {
|
|
13477
|
-
confidence: "high",
|
|
13478
13591
|
whyNow: approvalBlockingWhyNow(budgetOverride),
|
|
13479
13592
|
factors: budgetOverride ? ["budget stop", "approval", "operator decision"] : ["approval", "operator decision"],
|
|
13480
13593
|
...relationHintsForIssueTargets(linkedIssueIds) ?? {}
|
|
@@ -13482,7 +13595,6 @@ function approvalSemanticHints(budgetOverride, linkedIssueIds) {
|
|
|
13482
13595
|
}
|
|
13483
13596
|
function pendingApprovalSemanticHints() {
|
|
13484
13597
|
return {
|
|
13485
|
-
confidence: "high",
|
|
13486
13598
|
whyNow: pendingApprovalEventWhyNow(),
|
|
13487
13599
|
factors: ["pending approval"]
|
|
13488
13600
|
};
|
|
@@ -13490,7 +13602,6 @@ function pendingApprovalSemanticHints() {
|
|
|
13490
13602
|
function runFailureSemanticHints() {
|
|
13491
13603
|
return {
|
|
13492
13604
|
intentFrame: "failure",
|
|
13493
|
-
confidence: "high",
|
|
13494
13605
|
whyNow: runFailedWhyNow(),
|
|
13495
13606
|
factors: ["run failed", "operator review"]
|
|
13496
13607
|
};
|
package/package.json
CHANGED