@orboto/mcp 0.193.0 → 0.194.1
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/dist/event-bridge.js +1 -47
- package/dist/event-bridge.test.js +0 -19
- package/dist/http-transport-binding.test.js +1 -4
- package/dist/http-transport.js +1 -213
- package/dist/http-transport.test.js +0 -56
- package/dist/index.js +1 -48
- package/dist/input-schema.js +2 -76
- package/dist/input-schema.test.js +1 -15
- package/dist/instructions-budget.test.js +0 -2
- package/dist/manifest-size.test.js +3 -72
- package/dist/oauth-bootstrap.js +0 -47
- package/dist/oauth-bootstrap.test.js +4 -16
- package/dist/orboto-client.js +0 -30
- package/dist/orboto-client.test.js +0 -12
- package/dist/prompts.js +0 -39
- package/dist/protect-text-usage.test.js +1 -9
- package/dist/required-rules-wrapper.test.js +0 -5
- package/dist/required-rules.js +0 -2
- package/dist/resources.js +0 -62
- package/dist/resources.test.js +0 -16
- package/dist/response-budget.js +2 -150
- package/dist/response-budget.test.js +1 -32
- package/dist/server.js +4 -180
- package/dist/session-nudge.js +0 -9
- package/dist/session-nudge.test.js +2 -10
- package/dist/session-nudge.transport.test.js +0 -23
- package/dist/tool-annotations.test.js +0 -44
- package/dist/tool-docs.js +1 -12
- package/dist/tool-docs.test.js +2 -8
- package/dist/tool-parameters.test.js +0 -19
- package/dist/tools/absence-writes.js +1 -17
- package/dist/tools/admin-translations.js +0 -11
- package/dist/tools/admin-writes.js +0 -28
- package/dist/tools/admin-writes.test.js +0 -1
- package/dist/tools/agent-coordination.js +2 -24
- package/dist/tools/agent-coordination.test.js +0 -2
- package/dist/tools/agent-instructions.js +0 -10
- package/dist/tools/agent-instructions.test.js +0 -2
- package/dist/tools/agent-messages.js +0 -1
- package/dist/tools/ai-status.js +0 -14
- package/dist/tools/ai-usage.js +0 -10
- package/dist/tools/analytics.js +0 -2
- package/dist/tools/api-call.js +0 -12
- package/dist/tools/api-call.test.js +1 -2
- package/dist/tools/api-search.js +1 -6
- package/dist/tools/approvals.js +0 -6
- package/dist/tools/attach.js +0 -25
- package/dist/tools/backup.js +0 -13
- package/dist/tools/backup.test.js +1 -2
- package/dist/tools/bulk-create.js +1 -25
- package/dist/tools/bulk-create.test.js +0 -4
- package/dist/tools/bulk-writes.js +69 -61
- package/dist/tools/bulk-writes.test.js +34 -4
- package/dist/tools/check-similar.js +0 -17
- package/dist/tools/checklist-writes.js +82 -27
- package/dist/tools/checklist-writes.test.js +69 -1
- package/dist/tools/claim.js +1 -71
- package/dist/tools/claim.test.js +0 -2
- package/dist/tools/critical-path.js +0 -5
- package/dist/tools/critical-path.test.js +0 -3
- package/dist/tools/cross-project-links.ee.js +1 -24
- package/dist/tools/doc-attachments.js +0 -28
- package/dist/tools/doc-attachments.test.js +1 -4
- package/dist/tools/doc-comments.js +1 -37
- package/dist/tools/doc-comments.test.js +1 -2
- package/dist/tools/doc-edits.js +1 -31
- package/dist/tools/doc-edits.test.js +0 -9
- package/dist/tools/doc-export.js +0 -24
- package/dist/tools/doc-export.test.js +0 -12
- package/dist/tools/doc-revisions.js +0 -23
- package/dist/tools/docs-ai.js +0 -34
- package/dist/tools/docs-ai.test.js +0 -11
- package/dist/tools/docs.js +1 -75
- package/dist/tools/docs.test.js +1 -3
- package/dist/tools/embedding-status.js +0 -13
- package/dist/tools/embedding-status.test.js +0 -1
- package/dist/tools/feedback.js +1 -15
- package/dist/tools/feedback.test.js +0 -1
- package/dist/tools/get-checklists.js +6 -14
- package/dist/tools/get-project-primer.js +1 -25
- package/dist/tools/get-project.js +0 -17
- package/dist/tools/get-ticket.js +1 -61
- package/dist/tools/get-timer.js +0 -11
- package/dist/tools/git-health-reasons.js +0 -1
- package/dist/tools/identity.js +0 -13
- package/dist/tools/list-projects.js +0 -20
- package/dist/tools/list-projects.test.js +0 -1
- package/dist/tools/list-tickets.js +10 -9
- package/dist/tools/milestones-crud.test.js +0 -4
- package/dist/tools/milestones.js +0 -43
- package/dist/tools/my-tickets.js +0 -7
- package/dist/tools/phase-b-tools.test.js +14 -21
- package/dist/tools/primer-facts.js +1 -49
- package/dist/tools/primer-facts.test.js +0 -20
- package/dist/tools/project-listings.js +0 -25
- package/dist/tools/query.js +1 -12
- package/dist/tools/query.test.js +0 -8
- package/dist/tools/raci.js +0 -11
- package/dist/tools/requirements-spec.js +1 -12
- package/dist/tools/response-expand.js +0 -19
- package/dist/tools/review-policy.js +0 -24
- package/dist/tools/review-policy.test.js +0 -2
- package/dist/tools/search.js +1 -13
- package/dist/tools/session-start.js +1 -136
- package/dist/tools/session-start.test.js +1 -44
- package/dist/tools/set-parent.js +1 -19
- package/dist/tools/shared.js +0 -41
- package/dist/tools/shared.test.js +0 -4
- package/dist/tools/similar-projection.js +0 -15
- package/dist/tools/ticket-attachments.js +1 -25
- package/dist/tools/ticket-schedules.js +0 -9
- package/dist/tools/ticket-schedules.test.js +0 -1
- package/dist/tools/ticket-writes.js +1 -110
- package/dist/tools/ticket-writes.test.js +1 -15
- package/dist/tools/time-writes.js +26 -46
- package/dist/tools/time-writes.test.js +18 -0
- package/dist/tools/update-project.js +1 -27
- package/dist/tools/update-project.test.js +1 -1
- package/dist/tools/wiki.js +0 -8
- package/dist/tools/work-next.js +0 -5
- package/dist/tools/work-sessions-start.test.js +0 -2
- package/dist/tools/work-sessions.js +0 -48
- package/dist/tools/work-sessions.test.js +0 -5
- package/dist/toolset.js +2 -81
- package/dist/toolset.test.js +0 -20
- package/dist/version-guard.test.js +0 -4
- package/dist/version.js +0 -12
- package/dist/with-metrics-output-schema.test.js +1 -14
- package/dist/with-metrics.js +2 -74
- package/dist/with-metrics.test.js +7 -29
- package/package.json +1 -1
|
@@ -31,9 +31,6 @@ function stub(responses) {
|
|
|
31
31
|
ok: r.ok ?? true,
|
|
32
32
|
status: r.status ?? 200,
|
|
33
33
|
statusText: 'OK',
|
|
34
|
-
// `'json' in r` so an explicit `json: null` preserves null;
|
|
35
|
-
// `r.json ?? {}` would collapse null → {} and hide nullable
|
|
36
|
-
// endpoints (e.g. GET /time/timer returns the row or null).
|
|
37
34
|
json: async () => ('json' in r ? r.json : {}),
|
|
38
35
|
text: async () => '',
|
|
39
36
|
};
|
|
@@ -48,7 +45,6 @@ describe('orboto_get_project', () => {
|
|
|
48
45
|
{ json: PROJ },
|
|
49
46
|
{ json: [{ id: 'm1', name: 'v1', status: 'active', startDate: '2026-01-01', endDate: '2026-03-01' }] },
|
|
50
47
|
{ json: [{ id: 'l1', name: 'bug', color: '#f00' }] },
|
|
51
|
-
// Members endpoint response: nested user + role objects per the real schema.
|
|
52
48
|
{ json: [{ userId: 'u1', projectId: 'p1', roleId: 'r1',
|
|
53
49
|
user: { id: 'u1', email: 'ada@acme', fullName: 'Ada', avatarUrl: null },
|
|
54
50
|
role: { id: 'r1', name: 'developer' } }] },
|
|
@@ -75,8 +71,19 @@ describe('orboto_list_tickets', () => {
|
|
|
75
71
|
expect(calls[1]).toContain('statusCategory=in_progress');
|
|
76
72
|
expect(calls[1]).toContain('limit=10');
|
|
77
73
|
});
|
|
78
|
-
|
|
79
|
-
|
|
74
|
+
it('passes unscheduled=true for the backlog', async () => {
|
|
75
|
+
const calls = stub([
|
|
76
|
+
{ json: PROJ },
|
|
77
|
+
{ json: { items: [], nextCursor: null } },
|
|
78
|
+
]);
|
|
79
|
+
await makeListTicketsHandler(client)({ projectKey: 'ACME', unscheduled: true, statusCategory: 'todo' });
|
|
80
|
+
expect(calls[1]).toContain('unscheduled=true');
|
|
81
|
+
expect(calls[1]).toContain('statusCategory=todo');
|
|
82
|
+
});
|
|
83
|
+
it('refuses unscheduled together with milestone', async () => {
|
|
84
|
+
stub([{ json: PROJ }]);
|
|
85
|
+
await expect(makeListTicketsHandler(client)({ projectKey: 'ACME', unscheduled: true, milestone: 'ACME-M1' })).rejects.toThrow(/not both/);
|
|
86
|
+
});
|
|
80
87
|
it('resolves milestone KEY (ORB-1696)', async () => {
|
|
81
88
|
const calls = stub([
|
|
82
89
|
{ json: PROJ },
|
|
@@ -133,7 +140,6 @@ describe('orboto_list_tickets', () => {
|
|
|
133
140
|
]);
|
|
134
141
|
await expect(makeListTicketsHandler(client)({ projectKey: 'ACME', milestone: 'ghost' })).rejects.toThrow(/Milestone "ghost" not found/);
|
|
135
142
|
});
|
|
136
|
-
// ORB-1605 - the stalled-ingestion signal passes through into structuredContent.
|
|
137
143
|
it('surfaces waitingForGitIngestion per ticket', async () => {
|
|
138
144
|
stub([
|
|
139
145
|
{ json: PROJ },
|
|
@@ -157,8 +163,6 @@ describe('orboto_list_tickets', () => {
|
|
|
157
163
|
});
|
|
158
164
|
});
|
|
159
165
|
describe('orboto_get_ticket', () => {
|
|
160
|
-
// Fixture: empty children page (children METADATA is always fetched; the
|
|
161
|
-
// rows only surface with include: ["children"] since ORB-1698).
|
|
162
166
|
const NO_CHILDREN = { json: { items: [], nextCursor: null } };
|
|
163
167
|
it('ORB-1698 default card: only enriched + children + attachments are fetched - comments/checklists/git untouched', async () => {
|
|
164
168
|
const calls = stub([
|
|
@@ -174,7 +178,6 @@ describe('orboto_get_ticket', () => {
|
|
|
174
178
|
expect(calls.some((c) => c.includes('/checklists'))).toBe(false);
|
|
175
179
|
expect(calls.some((c) => c.includes('/git-activity'))).toBe(false);
|
|
176
180
|
const sc = res.structuredContent;
|
|
177
|
-
// Counts always present; bodies absent by default - never silently.
|
|
178
181
|
expect(sc.commentCount).toBe(3);
|
|
179
182
|
expect(sc.checklistProgress).toEqual({ done: 1, total: 2 });
|
|
180
183
|
expect(sc.attachmentCount).toBe(0);
|
|
@@ -230,8 +233,6 @@ describe('orboto_get_ticket', () => {
|
|
|
230
233
|
expect(sc.commentsHasMore).toBe(true);
|
|
231
234
|
expect(sc.parentTicket).toBeNull();
|
|
232
235
|
});
|
|
233
|
-
// ORB-1605 - the stalled-ingestion signal surfaces in both the header
|
|
234
|
-
// text and structuredContent.
|
|
235
236
|
it('surfaces waitingForGitIngestion in the header and structured content', async () => {
|
|
236
237
|
stub([
|
|
237
238
|
{ json: PROJ },
|
|
@@ -279,9 +280,7 @@ describe('orboto_get_ticket', () => {
|
|
|
279
280
|
stub([
|
|
280
281
|
{ json: PROJ },
|
|
281
282
|
{ json: { id: 't5', projectId: 'p1', ticketKey: 'ACME-5', title: 'Phase A', status: 'DONE', statusName: 'Done', statusCategory: 'done', type: 'task', priority: 'normal', gitActivityCount: 0, parentTicketId: 't1' } },
|
|
282
|
-
// Enriched by-id refetch
|
|
283
283
|
{ json: { id: 't5', projectId: 'p1', ticketKey: 'ACME-5', title: 'Phase A', status: 'DONE', statusName: 'Done', statusCategory: 'done', type: 'task', priority: 'normal' } },
|
|
284
|
-
// Parent ticket fetch
|
|
285
284
|
{ json: { id: 't1', projectId: 'p1', ticketKey: 'ACME-1', title: 'Epic foo', status: 'IN_PROGRESS', statusName: 'In Progress', statusCategory: 'in_progress' } },
|
|
286
285
|
NO_CHILDREN,
|
|
287
286
|
{ json: [] },
|
|
@@ -316,10 +315,7 @@ describe('orboto_get_ticket', () => {
|
|
|
316
315
|
it('ORB-1023: surfaces milestone (name, not UUID) + statusCategory from the enriched by-id refetch', async () => {
|
|
317
316
|
stub([
|
|
318
317
|
{ json: PROJ },
|
|
319
|
-
// Bare by-key resolver row: milestoneId set, but no milestoneName /
|
|
320
|
-
// statusCategory - this is the shape that dropped the milestone.
|
|
321
318
|
{ json: { id: 't1', projectId: 'p1', ticketKey: 'ACME-9', title: 'Bug', status: 'DONE', milestoneId: 'm1', gitActivityCount: 0, parentTicketId: null } },
|
|
322
|
-
// Enriched by-id refetch: carries statusCategory + the resolved name.
|
|
323
319
|
{ json: { id: 't1', projectId: 'p1', ticketKey: 'ACME-9', title: 'Bug', status: 'DONE', statusName: 'Done', statusCategory: 'done', type: 'bug', priority: 'normal', milestoneId: 'm1', milestoneName: 'Sprint 7' } },
|
|
324
320
|
NO_CHILDREN,
|
|
325
321
|
{ json: [] },
|
|
@@ -408,7 +404,6 @@ describe('milestone tools', () => {
|
|
|
408
404
|
{ json: { total: 12, byStatus: { TODO: 12 } } },
|
|
409
405
|
]);
|
|
410
406
|
const res = await makeGetMilestoneHandler(client)({ projectKey: 'ACME', milestone: 'acme-m19' });
|
|
411
|
-
// The resolver spans closed milestones too.
|
|
412
407
|
expect(calls[1]).toContain('includeClosed=true');
|
|
413
408
|
const sc = res.structuredContent;
|
|
414
409
|
expect(sc.milestone.name).toBe('Phase 35');
|
|
@@ -478,7 +473,6 @@ describe('orboto_get_checklists', () => {
|
|
|
478
473
|
id: 'cl1', title: 'Before ship', triggersDone: true,
|
|
479
474
|
progress: { done: 1, total: 2 },
|
|
480
475
|
items: [
|
|
481
|
-
// One plain item, one linked-to-another-ticket item.
|
|
482
476
|
{ id: 'i1', content: 'Docs updated', storedCompleted: true, effectiveCompleted: true,
|
|
483
477
|
linkedTicketId: null, linkedTicketKey: null, linkedTicketTitle: null, linkedTicketStatusCategory: null, sortOrder: 0 },
|
|
484
478
|
{ id: 'i2', content: 'Sub-task done', storedCompleted: false, effectiveCompleted: false,
|
|
@@ -517,11 +511,10 @@ describe('orboto_get_timer', () => {
|
|
|
517
511
|
expect(res.structuredContent).toEqual({ timer: null });
|
|
518
512
|
});
|
|
519
513
|
it('computes totalSeconds including elapsed time since startedAt', async () => {
|
|
520
|
-
const startedAt = new Date(Date.now() - 120_000).toISOString();
|
|
514
|
+
const startedAt = new Date(Date.now() - 120_000).toISOString();
|
|
521
515
|
stub([{ json: { id: 'tm1', userId: 'u1', ticketId: 't1', ticketTitle: 'Bug', startedAt, pausedAt: null, accumulatedSeconds: 300, description: 'debug' } }]);
|
|
522
516
|
const res = await makeGetTimerHandler(client)();
|
|
523
517
|
const sc = res.structuredContent;
|
|
524
|
-
// 300 accumulated + ~120 elapsed. Allow ±5s for test timing jitter.
|
|
525
518
|
expect(sc.timer.totalSeconds).toBeGreaterThanOrEqual(418);
|
|
526
519
|
expect(sc.timer.totalSeconds).toBeLessThanOrEqual(425);
|
|
527
520
|
expect(sc.timer.paused).toBe(false);
|
|
@@ -1,33 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ORB-510 / ORB-513 - primer-fact tools.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* `/primer-facts/:id` REST surface from ORB-511. Agents call these to
|
|
6
|
-
* record structured project facts - tech-stack details, conventions,
|
|
7
|
-
* deployment quirks - that the AI primer (ORB-512) renders at the top
|
|
8
|
-
* of every session. The skill rule (ORB-514) tells agents *when* to
|
|
9
|
-
* record; these tools are the *how*.
|
|
10
|
-
*
|
|
11
|
-
* All tool descriptions and parameter `.describe()` strings are in
|
|
12
|
-
* English so the international LLM tool-selection path is reliable.
|
|
13
|
-
*
|
|
14
|
-
* Wire order in server.ts:
|
|
15
|
-
* - orboto_primer_fact_list
|
|
16
|
-
* - orboto_primer_fact_add
|
|
17
|
-
* - orboto_primer_fact_update
|
|
18
|
-
* - orboto_primer_fact_supersede
|
|
19
|
-
* - orboto_primer_fact_verify
|
|
20
|
-
* - orboto_primer_fact_delete
|
|
4
|
+
* @see ORB-511, ORB-512, ORB-514
|
|
21
5
|
*/
|
|
22
6
|
import { z } from 'zod';
|
|
23
7
|
import { OrbotoApiError } from '../orboto-client.js';
|
|
24
8
|
import { resolveProjectByKey, sizeBlockResult } from './shared.js';
|
|
25
|
-
// ORB-1819 - the writing contract, verbatim in every write-tool
|
|
26
|
-
// description so the writing agent sees it at the moment of writing.
|
|
27
9
|
const FACT_WRITING_CONTRACT = ' Fact: <= 300 chars, one fact per key, the value is the fact - long material goes into a doc, the fact holds the doc key.';
|
|
28
|
-
// ---------------------------------------------------------------------------
|
|
29
|
-
// Shared types + helpers
|
|
30
|
-
// ---------------------------------------------------------------------------
|
|
31
10
|
const PRIMER_FACT_CATEGORIES = [
|
|
32
11
|
'tech_stack',
|
|
33
12
|
'conventions',
|
|
@@ -36,11 +15,7 @@ const PRIMER_FACT_CATEGORIES = [
|
|
|
36
15
|
'integrations',
|
|
37
16
|
'gotchas',
|
|
38
17
|
'commands',
|
|
39
|
-
// ORB-1403 - explicit non-functional requirements (performance, security,
|
|
40
|
-
// DSGVO, operations, i18n, ...) for the Pflichtenheft NFR chapter.
|
|
41
18
|
'non_functional',
|
|
42
|
-
// ORB-1413 - glossary/terminology facts for the Pflichtenheft glossary
|
|
43
|
-
// chapter (chapter 11) only, so they no longer double-render via `other`.
|
|
44
19
|
'glossary',
|
|
45
20
|
'other',
|
|
46
21
|
];
|
|
@@ -70,17 +45,12 @@ function summariseFact(f) {
|
|
|
70
45
|
if (f.supersededById)
|
|
71
46
|
markers.push('superseded');
|
|
72
47
|
const tag = markers.length > 0 ? ` _(${markers.join(', ')})_` : '';
|
|
73
|
-
// Single-line preview of the value - multi-line bodies get truncated
|
|
74
|
-
// so the list output stays scannable.
|
|
75
48
|
const valuePreview = f.value.length > 120 || f.value.includes('\n')
|
|
76
49
|
? `${f.value.replace(/\n/g, ' ').slice(0, 120)}…`
|
|
77
50
|
: f.value;
|
|
78
51
|
const scope = f.projectId === null ? '[workspace]' : '[project]';
|
|
79
52
|
return `- ${scope} **${f.category}/${f.key}**: ${valuePreview}${tag}`;
|
|
80
53
|
}
|
|
81
|
-
// ---------------------------------------------------------------------------
|
|
82
|
-
// orboto_primer_fact_list
|
|
83
|
-
// ---------------------------------------------------------------------------
|
|
84
54
|
export const primerFactListToolConfig = {
|
|
85
55
|
title: 'List structured project primer facts',
|
|
86
56
|
description: 'List structured project facts that feed the AI primer (tech stack, conventions, deployment, architecture, integrations, gotchas, commands). Use this to discover what the project has already documented before adding a new observation. Workspace-wide facts (applying to every project) are merged in by default; pass includeWorkspace=false to see only project-scoped rows.',
|
|
@@ -127,9 +97,6 @@ export function makePrimerFactListHandler(client) {
|
|
|
127
97
|
};
|
|
128
98
|
};
|
|
129
99
|
}
|
|
130
|
-
// ---------------------------------------------------------------------------
|
|
131
|
-
// orboto_primer_fact_add
|
|
132
|
-
// ---------------------------------------------------------------------------
|
|
133
100
|
export const primerFactAddToolConfig = {
|
|
134
101
|
title: 'Record a new project primer fact',
|
|
135
102
|
description: 'Record a new structured fact about the project that future AI agents should see at session start. Use when you learn something during work that is missing from the primer (a library version, a convention, a deployment quirk, an architecture decision). Note: the auto-generated primer does NOT automatically include CLAUDE.md / AGENTS.md content unless the operator configured repo briefings and the API host can read those files from disk - most deployments cannot. Treat key conventions documented in CLAUDE.md / AGENTS.md as fair game to record here so they survive cross-deployment. Set observed=true when you are recording from a bot/agent context - the fact will land as agent_observed and require operator verification before losing the (observed) marker.'
|
|
@@ -182,9 +149,6 @@ export function makePrimerFactAddHandler(client) {
|
|
|
182
149
|
};
|
|
183
150
|
};
|
|
184
151
|
}
|
|
185
|
-
// ---------------------------------------------------------------------------
|
|
186
|
-
// orboto_primer_fact_update
|
|
187
|
-
// ---------------------------------------------------------------------------
|
|
188
152
|
export const primerFactUpdateToolConfig = {
|
|
189
153
|
title: 'Update an existing primer fact',
|
|
190
154
|
description: 'Update an existing fact. Use this when the value has changed (e.g. version bumped) or you are renaming the key. Bumps last_verified_at automatically when the value changes; pure renames leave it untouched. To replace a fact while preserving history, use orboto_primer_fact_supersede instead.'
|
|
@@ -240,9 +204,6 @@ export function makePrimerFactUpdateHandler(client) {
|
|
|
240
204
|
};
|
|
241
205
|
};
|
|
242
206
|
}
|
|
243
|
-
// ---------------------------------------------------------------------------
|
|
244
|
-
// orboto_primer_fact_supersede
|
|
245
|
-
// ---------------------------------------------------------------------------
|
|
246
207
|
export const primerFactSupersedeToolConfig = {
|
|
247
208
|
title: 'Replace a primer fact while preserving history',
|
|
248
209
|
description: 'Replace a fact with a corrected version, preserving the old one in history (linked via superseded_by_id). Use when you discover a previously recorded fact was wrong; do not just delete it. The old row gets a tombstoned key suffix so the partial unique index does not collide; the new row starts unverified and needs operator verification before losing the (observed) marker.'
|
|
@@ -284,9 +245,6 @@ export function makePrimerFactSupersedeHandler(client) {
|
|
|
284
245
|
};
|
|
285
246
|
};
|
|
286
247
|
}
|
|
287
|
-
// ---------------------------------------------------------------------------
|
|
288
|
-
// orboto_primer_fact_verify
|
|
289
|
-
// ---------------------------------------------------------------------------
|
|
290
248
|
export const primerFactVerifyToolConfig = {
|
|
291
249
|
title: 'Verify an agent-observed primer fact',
|
|
292
250
|
description: 'Promote an agent-observed fact to verified. Operator-only action; bots without project:edit cannot self-verify. Removes the (observed) marker from the rendered primer and stamps verifiedBy / verifiedAt.',
|
|
@@ -314,9 +272,6 @@ export function makePrimerFactVerifyHandler(client) {
|
|
|
314
272
|
};
|
|
315
273
|
};
|
|
316
274
|
}
|
|
317
|
-
// ---------------------------------------------------------------------------
|
|
318
|
-
// orboto_primer_fact_delete
|
|
319
|
-
// ---------------------------------------------------------------------------
|
|
320
275
|
export const primerFactDeleteToolConfig = {
|
|
321
276
|
title: 'Delete a primer fact',
|
|
322
277
|
description: 'Hard-delete a fact. Prefer orboto_primer_fact_supersede when the fact is being replaced; only delete when the fact was wrong from the start or no longer relevant. Writes an audit-log entry; the row itself is gone (no soft-delete).',
|
|
@@ -328,9 +283,6 @@ export const primerFactDeleteToolConfig = {
|
|
|
328
283
|
};
|
|
329
284
|
export function makePrimerFactDeleteHandler(client) {
|
|
330
285
|
return async ({ factId, reason }) => {
|
|
331
|
-
// ORB-516 - pass the reason through so the audit-log entry
|
|
332
|
-
// captures it. Old API versions silently ignore the querystring
|
|
333
|
-
// so there's no compat risk.
|
|
334
286
|
const path = reason
|
|
335
287
|
? `/primer-facts/${factId}?reason=${encodeURIComponent(reason)}`
|
|
336
288
|
: `/primer-facts/${factId}`;
|
|
@@ -50,9 +50,6 @@ const factStub = {
|
|
|
50
50
|
createdAt: '2026-04-30T10:00:00Z',
|
|
51
51
|
updatedAt: '2026-04-30T10:00:00Z',
|
|
52
52
|
};
|
|
53
|
-
// ---------------------------------------------------------------------------
|
|
54
|
-
// orboto_primer_fact_list
|
|
55
|
-
// ---------------------------------------------------------------------------
|
|
56
53
|
describe('orboto_primer_fact_list', () => {
|
|
57
54
|
it('resolves projectKey then GETs /projects/<id>/primer-facts with filters', async () => {
|
|
58
55
|
const calls = stub([
|
|
@@ -118,9 +115,6 @@ describe('orboto_primer_fact_list', () => {
|
|
|
118
115
|
expect(result.success).toBe(false);
|
|
119
116
|
});
|
|
120
117
|
});
|
|
121
|
-
// ---------------------------------------------------------------------------
|
|
122
|
-
// orboto_primer_fact_add
|
|
123
|
-
// ---------------------------------------------------------------------------
|
|
124
118
|
describe('orboto_primer_fact_add', () => {
|
|
125
119
|
it('observed=false (default) sends source=manual', async () => {
|
|
126
120
|
const calls = stub([
|
|
@@ -189,9 +183,6 @@ describe('orboto_primer_fact_add', () => {
|
|
|
189
183
|
expect(result.success).toBe(false);
|
|
190
184
|
});
|
|
191
185
|
});
|
|
192
|
-
// ---------------------------------------------------------------------------
|
|
193
|
-
// orboto_primer_fact_update
|
|
194
|
-
// ---------------------------------------------------------------------------
|
|
195
186
|
describe('orboto_primer_fact_update', () => {
|
|
196
187
|
it('PATCHes /primer-facts/<id> with only the provided fields', async () => {
|
|
197
188
|
const calls = stub([{ json: { ...factStub, value: 'pnpm 10' } }]);
|
|
@@ -211,9 +202,6 @@ describe('orboto_primer_fact_update', () => {
|
|
|
211
202
|
await expect(makePrimerFactUpdateHandler(client)({ factId: 'missing', value: 'v' })).rejects.toThrow(/not found/);
|
|
212
203
|
});
|
|
213
204
|
});
|
|
214
|
-
// ---------------------------------------------------------------------------
|
|
215
|
-
// orboto_primer_fact_supersede
|
|
216
|
-
// ---------------------------------------------------------------------------
|
|
217
205
|
describe('orboto_primer_fact_supersede', () => {
|
|
218
206
|
it('POSTs /primer-facts/<id>/supersede with category + key + value', async () => {
|
|
219
207
|
const calls = stub([{ json: { ...factStub, id: 'new-uuid', value: 'pnpm 10' } }]);
|
|
@@ -237,9 +225,6 @@ describe('orboto_primer_fact_supersede', () => {
|
|
|
237
225
|
expect(schema.safeParse({ oldFactId: 'x' }).success).toBe(false);
|
|
238
226
|
});
|
|
239
227
|
});
|
|
240
|
-
// ---------------------------------------------------------------------------
|
|
241
|
-
// orboto_primer_fact_verify
|
|
242
|
-
// ---------------------------------------------------------------------------
|
|
243
228
|
describe('orboto_primer_fact_verify', () => {
|
|
244
229
|
it('POSTs /primer-facts/<id>/verify with empty body', async () => {
|
|
245
230
|
const calls = stub([{ json: { ...factStub, verified: true, verifiedBy: 'u1', verifiedAt: 'now' } }]);
|
|
@@ -249,9 +234,6 @@ describe('orboto_primer_fact_verify', () => {
|
|
|
249
234
|
expect(calls[0].body).toEqual({});
|
|
250
235
|
});
|
|
251
236
|
});
|
|
252
|
-
// ---------------------------------------------------------------------------
|
|
253
|
-
// orboto_primer_fact_delete
|
|
254
|
-
// ---------------------------------------------------------------------------
|
|
255
237
|
describe('orboto_primer_fact_delete', () => {
|
|
256
238
|
it('DELETEs /primer-facts/<id>', async () => {
|
|
257
239
|
const calls = stub([{ status: 204 }]);
|
|
@@ -275,8 +257,6 @@ describe('orboto_primer_fact_delete', () => {
|
|
|
275
257
|
await expect(makePrimerFactDeleteHandler(client)({ factId: 'missing' })).rejects.toThrow(/not found/);
|
|
276
258
|
});
|
|
277
259
|
});
|
|
278
|
-
// ORB-1819 - the writing-for-tokens size contract, passed through from the
|
|
279
|
-
// REST route's warn (200/201 + sizeWarning) / block (422) responses.
|
|
280
260
|
describe('ORB-1819 size contract', () => {
|
|
281
261
|
it('add surfaces a soft-limit sizeWarning in the success text, not as an error', async () => {
|
|
282
262
|
stub([
|
|
@@ -1,26 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ORB-799 - per-project metadata listings.
|
|
3
|
-
*
|
|
4
|
-
* Two cheap read tools that round-trip the project-scoped enum tables
|
|
5
|
-
* agents repeatedly need:
|
|
6
|
-
*
|
|
7
|
-
* - orboto_list_ticket_statuses - the workflow's status rows with
|
|
8
|
-
* their IDs, categories, colors, and terminal flag. Needed for
|
|
9
|
-
* typed-transition flows where the standard `move_ticket` (which
|
|
10
|
-
* picks the first status per category) isn't precise enough.
|
|
11
|
-
*
|
|
12
|
-
* - orboto_list_labels - the per-project label catalogue with
|
|
13
|
-
* colors. Lets agents see what labels exist before calling
|
|
14
|
-
* `create_ticket` with a `labels` array (unknown names error).
|
|
15
|
-
*
|
|
16
|
-
* Both mirror the wrapper's `statuses <project>` / `labels <project>`
|
|
17
|
-
* subcommands.
|
|
18
3
|
*/
|
|
19
4
|
import { z } from 'zod';
|
|
20
5
|
import { resolveProjectByKey } from './shared.js';
|
|
21
|
-
// ---------------------------------------------------------------------------
|
|
22
|
-
// orboto_list_ticket_statuses
|
|
23
|
-
// ---------------------------------------------------------------------------
|
|
24
6
|
export const listTicketStatusesToolConfig = {
|
|
25
7
|
title: 'List a project\'s ticket statuses',
|
|
26
8
|
description: 'Return the project\'s workflow rows - `{id, name, category, color, sortOrder, isTerminal}`. The `category` is the broad bucket (todo / in_progress / in_review / done / wont_fix); `name` is the workspace\'s display label (e.g. "Code Review"). Use the `id` to do typed transitions through `orboto_update_ticket` when the standard category-based move is too coarse.',
|
|
@@ -52,9 +34,6 @@ export function makeListTicketStatusesHandler(client) {
|
|
|
52
34
|
};
|
|
53
35
|
};
|
|
54
36
|
}
|
|
55
|
-
// ---------------------------------------------------------------------------
|
|
56
|
-
// orboto_list_labels
|
|
57
|
-
// ---------------------------------------------------------------------------
|
|
58
37
|
export const listLabelsToolConfig = {
|
|
59
38
|
title: 'List a project\'s labels',
|
|
60
39
|
description: 'Return the project\'s label catalogue - `{id, name, color}`. Labels must be referenced by exact name when assigning via `orboto_create_ticket` (the API errors on unknown names rather than auto-creating); use this tool to discover what\'s available before assigning.',
|
|
@@ -79,9 +58,6 @@ export function makeListLabelsHandler(client) {
|
|
|
79
58
|
};
|
|
80
59
|
};
|
|
81
60
|
}
|
|
82
|
-
// ---------------------------------------------------------------------------
|
|
83
|
-
// orboto_create_label - ORB-1041
|
|
84
|
-
// ---------------------------------------------------------------------------
|
|
85
61
|
export const createLabelToolConfig = {
|
|
86
62
|
title: 'Create a project label',
|
|
87
63
|
description: 'Create a label in a project so it can be assigned to tickets. Closes the gap where `orboto_create_ticket` errors on an unknown label name (labels are not auto-created). Idempotent: if a label with the same name already exists, it is returned instead of erroring. Needs project:edit.',
|
|
@@ -95,7 +71,6 @@ export const createLabelToolConfig = {
|
|
|
95
71
|
export function makeCreateLabelHandler(client) {
|
|
96
72
|
return async ({ projectKey, name, color }) => {
|
|
97
73
|
const project = await resolveProjectByKey(client, projectKey);
|
|
98
|
-
// Idempotent: return an existing same-name label rather than erroring.
|
|
99
74
|
const existing = await client.get(`/projects/${project.id}/labels`);
|
|
100
75
|
const match = existing.find((l) => l.name.toLowerCase() === name.toLowerCase());
|
|
101
76
|
if (match) {
|
package/dist/tools/query.js
CHANGED
|
@@ -1,17 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ORB-273 Phase F - `orboto_query`.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* tools (`orboto_list_tickets`, `orboto_my_tickets`) get too narrow:
|
|
6
|
-
* combined `assignee + dueDate + label + statusCategory` filters
|
|
7
|
-
* with explicit ORDER BY are awkward to express through tool
|
|
8
|
-
* arguments but trivial in OQL. Falls back to JQL syntax via
|
|
9
|
-
* `syntax: 'jql'` for migrants who copy-pasted a Jira query.
|
|
10
|
-
*
|
|
11
|
-
* The endpoint is `POST /query` (ORB-531) which honours PBAC,
|
|
12
|
-
* cursor-paginates, and rate-limits at 60/min. We expose the
|
|
13
|
-
* underlying envelope unchanged so a future "give me the next page"
|
|
14
|
-
* tool can plumb the cursor through.
|
|
4
|
+
* @see ORB-531
|
|
15
5
|
*/
|
|
16
6
|
import { z } from 'zod';
|
|
17
7
|
import { ticketLine, agentTicketListRow } from './shared.js';
|
|
@@ -57,7 +47,6 @@ export function makeQueryHandler(client) {
|
|
|
57
47
|
structuredContent: {
|
|
58
48
|
count: page.items.length,
|
|
59
49
|
nextCursor: page.nextCursor,
|
|
60
|
-
// ORB-1699 - shared lean row; verbose restores uuid/labels/minutes.
|
|
61
50
|
tickets: page.items.map((t) => agentTicketListRow(t, input.verbose ?? false)),
|
|
62
51
|
},
|
|
63
52
|
};
|
package/dist/tools/query.test.js
CHANGED
|
@@ -47,8 +47,6 @@ describe('tools/query', () => {
|
|
|
47
47
|
body: JSON.stringify({ oql: 'project = ORB', syntax: 'oql', cursor: undefined, limit: 25 }),
|
|
48
48
|
}));
|
|
49
49
|
expect(result.content[0]).toMatchObject({ type: 'text', text: expect.stringContaining('ORB-1') });
|
|
50
|
-
// ORB-1699 - the default row is the shared lean projection: no uuid,
|
|
51
|
-
// no labels, no minutes; assignee NAMES (fullName || email).
|
|
52
50
|
expect(result.structuredContent).toMatchObject({
|
|
53
51
|
count: 1,
|
|
54
52
|
nextCursor: null,
|
|
@@ -88,10 +86,6 @@ describe('tools/query', () => {
|
|
|
88
86
|
}));
|
|
89
87
|
});
|
|
90
88
|
});
|
|
91
|
-
// ORB-1699 - the ONE row builder, asserted from one place: the lean shape
|
|
92
|
-
// omits uuid/timestamps/minutes; verbose restores them. list_tickets,
|
|
93
|
-
// my_tickets and query all consume agentTicketListRow, so this single
|
|
94
|
-
// test pins the row for all three.
|
|
95
89
|
import { agentTicketListRow } from './shared.js';
|
|
96
90
|
describe('agentTicketListRow (ORB-1699)', () => {
|
|
97
91
|
const FULL = {
|
|
@@ -104,8 +98,6 @@ describe('agentTicketListRow (ORB-1699)', () => {
|
|
|
104
98
|
};
|
|
105
99
|
it('lean row: decision fields only, defaults omitted', () => {
|
|
106
100
|
const row = agentTicketListRow(FULL);
|
|
107
|
-
// priority 'high' and dueDate present -> included; type stays only
|
|
108
|
-
// when it deviates from 'task'; status NAME is verbose-only.
|
|
109
101
|
expect(Object.keys(row).sort()).toEqual(['assigneeNames', 'dueDate', 'key', 'priority', 'statusCategory', 'title']);
|
|
110
102
|
expect(row.assigneeNames).toEqual(['Ada']);
|
|
111
103
|
});
|
package/dist/tools/raci.js
CHANGED
|
@@ -17,9 +17,6 @@ async function resolveMemberId(client, projectId, email) {
|
|
|
17
17
|
throw new Error(`No project member with email "${email}".`);
|
|
18
18
|
return m.userId;
|
|
19
19
|
}
|
|
20
|
-
// ---------------------------------------------------------------------------
|
|
21
|
-
// orboto_raci - read the matrix
|
|
22
|
-
// ---------------------------------------------------------------------------
|
|
23
20
|
export const raciToolConfig = {
|
|
24
21
|
title: 'RACI matrix',
|
|
25
22
|
description: "Read a project's RACI matrix (tickets x members, cells = R/A/C/I). `milestone` scopes to one milestone; `epicsOnly` narrows rows to epics. RACI is OPT-IN per project: only use this when the project has RACI enabled (check `raciEnabled` on orboto_get_project / the primer). Do NOT raise or suggest RACI on projects that haven't enabled it. Returns empty when RACI is off.",
|
|
@@ -35,8 +32,6 @@ export function makeRaciHandler(client) {
|
|
|
35
32
|
const project = await resolveProjectByKey(client, input.projectKey);
|
|
36
33
|
const qs = new URLSearchParams();
|
|
37
34
|
if (input.milestone) {
|
|
38
|
-
// ORB-1696 - shared resolver: key (ORB-M3), name or UUID, ambiguous
|
|
39
|
-
// name -> explicit error. Matches create_ticket/set_milestone/OQL.
|
|
40
35
|
const m = await resolveMilestoneByNameOrId(client, project.id, input.milestone);
|
|
41
36
|
qs.set('milestoneId', m.id);
|
|
42
37
|
}
|
|
@@ -49,7 +44,6 @@ export function makeRaciHandler(client) {
|
|
|
49
44
|
structuredContent: { raciEnabled: false, projectKey: project.key, members: [], rows: [] },
|
|
50
45
|
};
|
|
51
46
|
}
|
|
52
|
-
// Compact text rendering: one line per ticket listing each held role.
|
|
53
47
|
const lines = data.rows.map((r) => {
|
|
54
48
|
const byUser = new Map(data.members.map((m) => [m.userId, m.fullName]));
|
|
55
49
|
const held = Object.entries(r.cells)
|
|
@@ -64,9 +58,6 @@ export function makeRaciHandler(client) {
|
|
|
64
58
|
};
|
|
65
59
|
};
|
|
66
60
|
}
|
|
67
|
-
// ---------------------------------------------------------------------------
|
|
68
|
-
// orboto_set_raci - write a role
|
|
69
|
-
// ---------------------------------------------------------------------------
|
|
70
61
|
export const setRaciToolConfig = {
|
|
71
62
|
title: 'Set a RACI role',
|
|
72
63
|
description: "Set a person's RACI role on a ticket: R (Responsible), A (Accountable, max one per ticket), C (Consulted), or I (Informed). RACI is OPT-IN per project: only use this when the project has RACI enabled (`raciEnabled` true) - do NOT suggest or set RACI on projects that haven't opted in. Resolves the user by email within the ticket's project. Requires ticket:manage_raci. A second Accountable is rejected with the current holder's name.",
|
|
@@ -86,8 +77,6 @@ export function makeSetRaciHandler(client) {
|
|
|
86
77
|
}
|
|
87
78
|
catch (err) {
|
|
88
79
|
if (err instanceof OrbotoApiError && err.status === 409) {
|
|
89
|
-
// Single-A violation or RACI-not-enabled - surface the clean message
|
|
90
|
-
// the API put in the response body.
|
|
91
80
|
let msg = err.body;
|
|
92
81
|
try {
|
|
93
82
|
msg = JSON.parse(err.body).error ?? err.body;
|
|
@@ -1,18 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ORB-1409 (epic ORB-1390) - `orboto_requirements_spec`.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* numbered functional requirements (FA-1, FA-1.1...) each traceable to its
|
|
6
|
-
* ticket and tagged muss/soll/kann from priority, plus non-functional
|
|
7
|
-
* requirements distilled from primer facts. `outlineVariant` picks the chapter
|
|
8
|
-
* naming/order: `neutral` (default), `industry` (VDI-3694 style), or `software`
|
|
9
|
-
* (IEEE-830 style). Private tickets/milestones are always excluded server-side.
|
|
10
|
-
* Wraps `POST /projects/:id/requirements-spec/generate` with `format: 'markdown'`.
|
|
11
|
-
* Money price mode needs budget:view on top of requirements_spec:generate (the
|
|
12
|
-
* API returns 403 otherwise).
|
|
13
|
-
*
|
|
14
|
-
* The route existed since ORB-1391; ORB-1409 fills in the missing MCP + skill
|
|
15
|
-
* surfaces (the Pflichtenheft feature shipped route + chat only).
|
|
4
|
+
* @see ORB-1391
|
|
16
5
|
*/
|
|
17
6
|
import { z } from 'zod';
|
|
18
7
|
import { OrbotoApiError } from '../orboto-client.js';
|
|
@@ -1,20 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ORB-1697 - `orboto_response_expand`: the way back from a truncated
|
|
3
3
|
* response.
|
|
4
|
-
*
|
|
5
|
-
* The central response budget (`../response-budget.ts`) caps what a
|
|
6
|
-
* single tool result may inject into the session, because a result is
|
|
7
|
-
* re-sent on every later request and therefore costs its size times the
|
|
8
|
-
* turns that follow it. A cap is only acceptable if nothing is lost, so
|
|
9
|
-
* every truncated response carries a handle, and this tool serves the
|
|
10
|
-
* omitted remainder from the in-process store in budget-sized chunks.
|
|
11
|
-
*
|
|
12
|
-
* Deliberately NOT a REST call: the handle addresses the payload the
|
|
13
|
-
* ORIGINAL call already produced, so expanding cannot return anything
|
|
14
|
-
* the caller was not already authorised to see, and it costs no
|
|
15
|
-
* additional API round-trip. The store is per-process and short-lived
|
|
16
|
-
* (15 min, 16 payloads) - an expired handle is answered with "re-run the
|
|
17
|
-
* original tool", never with a wrong or stale chunk.
|
|
18
4
|
*/
|
|
19
5
|
import { z } from 'zod';
|
|
20
6
|
import { budgetFor, readPayload, resolvePath, } from '../response-budget.js';
|
|
@@ -48,8 +34,6 @@ export function makeResponseExpandHandler() {
|
|
|
48
34
|
if (!stored)
|
|
49
35
|
return expired(input.handle);
|
|
50
36
|
const omitted = stored.omitted ?? [];
|
|
51
|
-
// No path - list what can be expanded. Cheap by construction: paths
|
|
52
|
-
// and counts only, never the content itself.
|
|
53
37
|
if (!input.path) {
|
|
54
38
|
const lines = [
|
|
55
39
|
`# Truncated content available for handle "${input.handle}"`,
|
|
@@ -77,9 +61,6 @@ export function makeResponseExpandHandler() {
|
|
|
77
61
|
return { isError: true, content: [{ type: 'text', text }] };
|
|
78
62
|
}
|
|
79
63
|
const full = typeof raw === 'string' ? raw : JSON.stringify(raw, null, 1) ?? '';
|
|
80
|
-
// Leave room for the framing lines so a chunk plus its header still
|
|
81
|
-
// fits this tool's own budget - a continuation tool that gets
|
|
82
|
-
// truncated itself would be a loop, not a fix.
|
|
83
64
|
const chunkSize = Math.max(500, budgetFor('orboto_response_expand') - 800);
|
|
84
65
|
const cursor = Math.min(input.cursor ?? 0, full.length);
|
|
85
66
|
const chunk = full.slice(cursor, cursor + chunkSize);
|
|
@@ -1,26 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ORB-1615 - structural review policy tools.
|
|
3
|
-
*
|
|
4
|
-
* - orboto_review_fingerprint - hash exact raw diff text into a
|
|
5
|
-
* canonical fingerprint (+ real size
|
|
6
|
-
* metrics), server-side (one algorithm,
|
|
7
|
-
* not one per client).
|
|
8
|
-
* - orboto_review_policy_check - the consult-before-invoking-a-model
|
|
9
|
-
* call: what risk level does this ticket carry, and (if a fingerprint
|
|
10
|
-
* is supplied) is there already a valid approval for THIS exact diff.
|
|
11
|
-
* - orboto_review_approval_record - record an approve/reject decision
|
|
12
|
-
* against a ticket's diff fingerprint.
|
|
13
|
-
*
|
|
14
|
-
* Rule CONFIGURATION (path pattern / deliveryMode / size -> risk level)
|
|
15
|
-
* stays out of the MCP surface, same call as approval_policies' CRUD in
|
|
16
|
-
* tools/approvals.ts: it's project-settings admin config, not something an
|
|
17
|
-
* autonomous agent tool should expose.
|
|
18
3
|
*/
|
|
19
4
|
import { z } from 'zod';
|
|
20
5
|
import { resolveTicketByKey } from './shared.js';
|
|
21
|
-
// ---------------------------------------------------------------------------
|
|
22
|
-
// orboto_review_fingerprint
|
|
23
|
-
// ---------------------------------------------------------------------------
|
|
24
6
|
export const reviewFingerprintToolConfig = {
|
|
25
7
|
title: 'Compute a canonical diff fingerprint',
|
|
26
8
|
description: 'Hash the exact submitted diff text server-side with sha256-diff-v2. Every text change, including indentation, blank lines, binary blob identities, mode/rename metadata and file order, changes the fingerprint. This is submitted-text identity, NOT independent repository attestation. Returns size/path metrics too. Preserve the complete opaque version-prefixed `fingerprint` for `orboto_review_policy_check` and `orboto_review_approval_record`; legacy v1 approvals remain history only and must be reviewed again.',
|
|
@@ -41,9 +23,6 @@ export function makeReviewFingerprintHandler(client) {
|
|
|
41
23
|
};
|
|
42
24
|
};
|
|
43
25
|
}
|
|
44
|
-
// ---------------------------------------------------------------------------
|
|
45
|
-
// orboto_review_policy_check
|
|
46
|
-
// ---------------------------------------------------------------------------
|
|
47
26
|
export const reviewPolicyCheckToolConfig = {
|
|
48
27
|
title: 'Check a ticket\'s review policy before invoking a reviewer',
|
|
49
28
|
description: 'Consult a ticket\'s structural review policy BEFORE spawning a review session or invoking a model. Called with no diff context (the common case, right after picking up a ticket) it resolves the per-ticket override or a deliveryMode-only rule; passing `paths`/`linesChanged` (from `orboto_review_fingerprint`) additionally matches path/size-scoped rules and, if `fingerprint` is passed too, checks whether a VALID approval already covers this exact diff - so a low-risk ticket, or one already reviewed at this fingerprint, can close without spawning a reviewer. `riskLevel: "required"` with `source: "fail_safe"` means the policy engine itself errored - treat that as "review required", never as "no review needed".',
|
|
@@ -71,9 +50,6 @@ export function makeReviewPolicyCheckHandler(client) {
|
|
|
71
50
|
};
|
|
72
51
|
};
|
|
73
52
|
}
|
|
74
|
-
// ---------------------------------------------------------------------------
|
|
75
|
-
// orboto_review_approval_record
|
|
76
|
-
// ---------------------------------------------------------------------------
|
|
77
53
|
export const reviewApprovalRecordToolConfig = {
|
|
78
54
|
title: 'Record a review decision against a diff fingerprint',
|
|
79
55
|
description: 'Record your review verdict (approve/reject) against the complete version-prefixed fingerprint from orboto_review_fingerprint. A current-algorithm approval is reusable only for identical submitted diff text; even whitespace or file-order changes require a fresh review. Legacy/unversioned records remain history only. This advisory review ledger does not itself enforce ticket closure. Requires ticket:record_review_approval.',
|
|
@@ -66,8 +66,6 @@ describe('orboto_review_approval_record', () => {
|
|
|
66
66
|
const res = await makeReviewApprovalRecordHandler(client)({ ticketKey: 'ACME-1', fingerprint: FINGERPRINT, decision: 'approved', note: 'looks good' });
|
|
67
67
|
const record = calls.find((c) => c.method === 'POST' && c.url.includes('review-approvals'));
|
|
68
68
|
expect(record?.body).toMatchObject({ fingerprint: FINGERPRINT, decision: 'approved', note: 'looks good' });
|
|
69
|
-
// ticketKey must NOT leak into the request body sent to the API - the
|
|
70
|
-
// route resolves the ticket from the URL, not the payload.
|
|
71
69
|
expect(record?.body).not.toHaveProperty('ticketKey');
|
|
72
70
|
expect(res.content[0].text).toContain('approved');
|
|
73
71
|
});
|
package/dist/tools/search.js
CHANGED
|
@@ -1,17 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ORB-244 Phase B - `orboto_search`.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* to the existing `/search` route (Phase 21 global search) which
|
|
6
|
-
* already respects PBAC visibility (private tickets, internal
|
|
7
|
-
* comments, doc ACL).
|
|
8
|
-
*
|
|
9
|
-
* ORB-272: /search is cursor-paginated. Response shape is
|
|
10
|
-
* `{items, nextCursor, total}`. We surface `total` so the model
|
|
11
|
-
* knows how many hits exist globally, but don't expose `cursor` on
|
|
12
|
-
* the tool input - repeat MCP tool calls to walk a search cursor is
|
|
13
|
-
* an awkward UX. Users who need to see more narrow the query or
|
|
14
|
-
* open the full-page /search in the web UI.
|
|
4
|
+
* @see ORB-272
|
|
15
5
|
*/
|
|
16
6
|
import { z } from 'zod';
|
|
17
7
|
import { resolveProjectByKey } from './shared.js';
|
|
@@ -49,8 +39,6 @@ export function makeSearchHandler(client) {
|
|
|
49
39
|
? `No hits for "${input.query}".`
|
|
50
40
|
: `Hits${headerHint}${relaxedNote}:\n\n` + res.items.map((h) => {
|
|
51
41
|
const tag = h.type.toUpperCase();
|
|
52
|
-
// ORB-1084 - non-ticket hits carry the FULL id: the truncated
|
|
53
|
-
// form was unusable as input for the doc write tools.
|
|
54
42
|
const ident = h.ticketKey ?? h.id;
|
|
55
43
|
const project = h.projectName ? ` · ${h.projectName}` : '';
|
|
56
44
|
return `- [${tag} ${ident}${project}] ${h.title}\n ${h.excerpt}`;
|