@atolis-hq/wake 0.2.33 → 0.2.35
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/src/adapters/claude/claude-runner.js +3 -0
- package/dist/src/adapters/codex/codex-runner.js +8 -1
- package/dist/src/adapters/cursor/cursor-runner.js +8 -1
- package/dist/src/adapters/runner/stage-prompt.js +12 -0
- package/dist/src/core/policy-engine.js +10 -1
- package/dist/src/core/scheduled-workflow-source.js +199 -0
- package/dist/src/core/tick-runner.js +8 -0
- package/dist/src/domain/schema.js +22 -0
- package/dist/src/domain/workflows.js +4 -0
- package/dist/src/main.js +9 -0
- package/dist/src/version.js +1 -1
- package/package.json +1 -1
- package/prompts/triage-assign.md +33 -0
|
@@ -172,6 +172,9 @@ export function createClaudeRunner(options) {
|
|
|
172
172
|
projection: input.projection,
|
|
173
173
|
mode: isResume ? 'resume' : 'start',
|
|
174
174
|
config: input.config,
|
|
175
|
+
...(input.promptContextOverrides === undefined
|
|
176
|
+
? {}
|
|
177
|
+
: { contextOverrides: input.promptContextOverrides }),
|
|
175
178
|
...(input.mergeConflictDetected === true ? { mergeConflictDetected: true } : {}),
|
|
176
179
|
...(input.upstreamChanges === undefined ? {} : { upstreamChanges: input.upstreamChanges }),
|
|
177
180
|
});
|
|
@@ -215,7 +215,14 @@ export function createCodexRunner(options) {
|
|
|
215
215
|
...(input.workspaceMode === undefined ? {} : { workspaceMode: input.workspaceMode }),
|
|
216
216
|
...(input.mergeConflictDetected === true ? { mergeConflictDetected: true } : {}),
|
|
217
217
|
...(input.upstreamChanges === undefined ? {} : { upstreamChanges: input.upstreamChanges }),
|
|
218
|
-
...(toolCapabilityNote !== undefined
|
|
218
|
+
...(toolCapabilityNote !== undefined || input.promptContextOverrides !== undefined
|
|
219
|
+
? {
|
|
220
|
+
contextOverrides: {
|
|
221
|
+
...input.promptContextOverrides,
|
|
222
|
+
...(toolCapabilityNote === undefined ? {} : { toolCapabilityNote }),
|
|
223
|
+
},
|
|
224
|
+
}
|
|
225
|
+
: {}),
|
|
219
226
|
});
|
|
220
227
|
const model = resolveModel({
|
|
221
228
|
action: input.action,
|
|
@@ -173,7 +173,14 @@ export function createCursorRunner(options) {
|
|
|
173
173
|
...(input.workspaceMode === undefined ? {} : { workspaceMode: input.workspaceMode }),
|
|
174
174
|
...(input.mergeConflictDetected === true ? { mergeConflictDetected: true } : {}),
|
|
175
175
|
...(input.upstreamChanges === undefined ? {} : { upstreamChanges: input.upstreamChanges }),
|
|
176
|
-
...(toolCapabilityNote !== undefined
|
|
176
|
+
...(toolCapabilityNote !== undefined || input.promptContextOverrides !== undefined
|
|
177
|
+
? {
|
|
178
|
+
contextOverrides: {
|
|
179
|
+
...input.promptContextOverrides,
|
|
180
|
+
...(toolCapabilityNote === undefined ? {} : { toolCapabilityNote }),
|
|
181
|
+
},
|
|
182
|
+
}
|
|
183
|
+
: {}),
|
|
177
184
|
});
|
|
178
185
|
const model = resolveModel({ action: input.action, settings: options.settings });
|
|
179
186
|
const cwd = input.workspacePath ?? options.cwd;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { defaultAgentIdentity } from '../../domain/schema.js';
|
|
2
|
+
import { alwaysManualIgnoredLabels } from '../../core/policy-engine.js';
|
|
2
3
|
import { chooseAction, workflowForProjection } from '../../domain/workflows.js';
|
|
3
4
|
import { branchNameForIssue } from '../git/git-workspace-manager.js';
|
|
4
5
|
import { loadPromptTemplate, renderPromptTemplate } from './prompt-templates.js';
|
|
@@ -165,6 +166,17 @@ export async function buildStagePrompt(input) {
|
|
|
165
166
|
isStart: mode === 'start',
|
|
166
167
|
isResume: mode === 'resume',
|
|
167
168
|
};
|
|
169
|
+
if (input.action === 'triage-assign' && input.config !== undefined) {
|
|
170
|
+
const ignoredLabels = [
|
|
171
|
+
...new Set([
|
|
172
|
+
...alwaysManualIgnoredLabels,
|
|
173
|
+
...input.config.sources.github.policy.ignoredLabels,
|
|
174
|
+
]),
|
|
175
|
+
];
|
|
176
|
+
context.triageIgnoredLabels = ignoredLabels;
|
|
177
|
+
context.triageIgnoredLabelsJson = JSON.stringify(ignoredLabels);
|
|
178
|
+
context.triageReposJson = JSON.stringify(input.config.sources.github.repos);
|
|
179
|
+
}
|
|
168
180
|
if (resolvedWorkspaceMode === 'branch') {
|
|
169
181
|
context.branch = branchNameForIssue(input.projection.issue.number);
|
|
170
182
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { awaitingApprovalRunnerSentinel, failedRunnerSentinel } from '../domain/stages.js';
|
|
2
2
|
import { resolveCustomCommand } from '../domain/custom-commands.js';
|
|
3
3
|
import { builtInDefaultWorkflowDefinition, chooseAction as chooseWorkflowAction, isKnownWorkflowStage, selectWorkflowForEvent, workflowForProjection, } from '../domain/workflows.js';
|
|
4
|
+
export const alwaysManualIgnoredLabels = ['security', 'wake:manual', 'wake:always-manual'];
|
|
4
5
|
function isAwaitingApproval(issue) {
|
|
5
6
|
const context = issue.context;
|
|
6
7
|
return context.lastRunSentinel === awaitingApprovalRunnerSentinel;
|
|
@@ -36,7 +37,8 @@ function labelsAndAssigneesQualify(input) {
|
|
|
36
37
|
if (input.requiredLabels.some((label) => !labels.has(label))) {
|
|
37
38
|
return false;
|
|
38
39
|
}
|
|
39
|
-
|
|
40
|
+
const ignoredLabels = new Set([...input.ignoredLabels, ...alwaysManualIgnoredLabels]);
|
|
41
|
+
if ([...ignoredLabels].some((label) => labels.has(label))) {
|
|
40
42
|
return false;
|
|
41
43
|
}
|
|
42
44
|
if (input.requiredAssignees.length > 0 &&
|
|
@@ -67,6 +69,9 @@ export function createPolicyEngine() {
|
|
|
67
69
|
if (issue.issue.state !== 'open') {
|
|
68
70
|
return false;
|
|
69
71
|
}
|
|
72
|
+
if (issue.issue.labels.some((label) => alwaysManualIgnoredLabels.includes(label))) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
70
75
|
const context = issue.context;
|
|
71
76
|
if (config.workflowSelectors.length > 0) {
|
|
72
77
|
return typeof context.workflow === 'string';
|
|
@@ -240,6 +245,10 @@ export function createPolicyEngine() {
|
|
|
240
245
|
return false;
|
|
241
246
|
}
|
|
242
247
|
const kind = resourceUri.split(':')[1];
|
|
248
|
+
if (kind === 'schedule') {
|
|
249
|
+
const workflow = unresolved.payload.workflow;
|
|
250
|
+
return typeof workflow === 'string' && config.workflows[workflow] !== undefined;
|
|
251
|
+
}
|
|
243
252
|
if (config.workflowSelectors.length > 0) {
|
|
244
253
|
return selectWorkflowForEvent(unresolved, config) !== null;
|
|
245
254
|
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { createUnkeyedEventEnvelope } from '../lib/event-log.js';
|
|
2
|
+
import { buildResourceUri } from '../domain/resource-uri.js';
|
|
3
|
+
import { readJsonFile, writeJsonFile } from '../lib/json-file.js';
|
|
4
|
+
import { createWakePaths } from '../lib/paths.js';
|
|
5
|
+
import { isMissingPathError } from '../lib/state-health.js';
|
|
6
|
+
const syntheticRepo = 'wake/internal';
|
|
7
|
+
const syntheticIssueNumber = 1;
|
|
8
|
+
function parseCronField(raw, min, max) {
|
|
9
|
+
const allowed = new Set();
|
|
10
|
+
for (const part of raw.split(',')) {
|
|
11
|
+
const trimmed = part.trim();
|
|
12
|
+
if (trimmed.length === 0) {
|
|
13
|
+
throw new Error(`invalid cron field "${raw}"`);
|
|
14
|
+
}
|
|
15
|
+
const stepMatch = /^(.*)\/(\d+)$/.exec(trimmed);
|
|
16
|
+
const base = stepMatch?.[1] ?? trimmed;
|
|
17
|
+
const step = stepMatch === null ? 1 : Number(stepMatch[2]);
|
|
18
|
+
if (!Number.isInteger(step) || step <= 0) {
|
|
19
|
+
throw new Error(`invalid cron step "${trimmed}"`);
|
|
20
|
+
}
|
|
21
|
+
const range = base === '*'
|
|
22
|
+
? [min, max]
|
|
23
|
+
: (() => {
|
|
24
|
+
const match = /^(\d+)(?:-(\d+))?$/.exec(base);
|
|
25
|
+
if (match === null) {
|
|
26
|
+
throw new Error(`invalid cron segment "${trimmed}"`);
|
|
27
|
+
}
|
|
28
|
+
const start = Number(match[1]);
|
|
29
|
+
const end = match[2] === undefined ? start : Number(match[2]);
|
|
30
|
+
if (start < min || end > max || start > end) {
|
|
31
|
+
throw new Error(`cron segment "${trimmed}" is outside ${min}-${max}`);
|
|
32
|
+
}
|
|
33
|
+
return [start, end];
|
|
34
|
+
})();
|
|
35
|
+
for (let value = range[0]; value <= range[1]; value += step) {
|
|
36
|
+
allowed.add(value);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return { matches: (value) => allowed.has(value) };
|
|
40
|
+
}
|
|
41
|
+
function parseCron(cron) {
|
|
42
|
+
const fields = cron.trim().split(/\s+/);
|
|
43
|
+
if (fields.length !== 5) {
|
|
44
|
+
throw new Error(`cron schedule must have five fields: ${cron}`);
|
|
45
|
+
}
|
|
46
|
+
return [
|
|
47
|
+
parseCronField(fields[0], 0, 59),
|
|
48
|
+
parseCronField(fields[1], 0, 23),
|
|
49
|
+
parseCronField(fields[2], 1, 31),
|
|
50
|
+
parseCronField(fields[3], 1, 12),
|
|
51
|
+
parseCronField(fields[4], 0, 6),
|
|
52
|
+
];
|
|
53
|
+
}
|
|
54
|
+
function matchesCron(date, cron) {
|
|
55
|
+
return (cron[0].matches(date.getUTCMinutes()) &&
|
|
56
|
+
cron[1].matches(date.getUTCHours()) &&
|
|
57
|
+
cron[2].matches(date.getUTCDate()) &&
|
|
58
|
+
cron[3].matches(date.getUTCMonth() + 1) &&
|
|
59
|
+
cron[4].matches(date.getUTCDay()));
|
|
60
|
+
}
|
|
61
|
+
function floorToMinute(date) {
|
|
62
|
+
const copy = new Date(date);
|
|
63
|
+
copy.setUTCSeconds(0, 0);
|
|
64
|
+
return copy;
|
|
65
|
+
}
|
|
66
|
+
function previousMatchingSlot(input) {
|
|
67
|
+
const parsed = parseCron(input.cron);
|
|
68
|
+
const slot = floorToMinute(input.now);
|
|
69
|
+
const afterMs = input.after === undefined ? undefined : Date.parse(input.after);
|
|
70
|
+
const lookbackMinutes = 366 * 24 * 60;
|
|
71
|
+
for (let checked = 0; checked < lookbackMinutes; checked += 1) {
|
|
72
|
+
const slotIso = slot.toISOString();
|
|
73
|
+
if (afterMs !== undefined && slot.getTime() <= afterMs) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
if (matchesCron(slot, parsed)) {
|
|
77
|
+
return slotIso;
|
|
78
|
+
}
|
|
79
|
+
slot.setUTCMinutes(slot.getUTCMinutes() - 1);
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
function triggerStateFile(wakeRoot, workflow) {
|
|
84
|
+
return `${createWakePaths(wakeRoot).dataRoot}/triggers/${workflow}.json`;
|
|
85
|
+
}
|
|
86
|
+
async function readTriggerState(wakeRoot, workflow) {
|
|
87
|
+
try {
|
|
88
|
+
const raw = await readJsonFile(triggerStateFile(wakeRoot, workflow));
|
|
89
|
+
if (raw === null || typeof raw !== 'object') {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
const record = raw;
|
|
93
|
+
return record.schemaVersion === 1 &&
|
|
94
|
+
record.workflow === workflow &&
|
|
95
|
+
typeof record.lastFiredSlot === 'string' &&
|
|
96
|
+
typeof record.updatedAt === 'string'
|
|
97
|
+
? {
|
|
98
|
+
schemaVersion: 1,
|
|
99
|
+
workflow,
|
|
100
|
+
lastFiredSlot: record.lastFiredSlot,
|
|
101
|
+
updatedAt: record.updatedAt,
|
|
102
|
+
}
|
|
103
|
+
: null;
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
if (isMissingPathError(error)) {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
throw error;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function scheduledEventId(workflow, slot) {
|
|
113
|
+
return `scheduled-workflow-${workflow}-${slot.replace(/[^a-z0-9]+/gi, '-')}`;
|
|
114
|
+
}
|
|
115
|
+
function scheduledResourceUri(workflow, slot) {
|
|
116
|
+
return buildResourceUri('wake', 'schedule', `${workflow}@${slot}`);
|
|
117
|
+
}
|
|
118
|
+
function syntheticTicket(input) {
|
|
119
|
+
const eventId = scheduledEventId(input.workflow, input.slot);
|
|
120
|
+
const resourceUri = scheduledResourceUri(input.workflow, input.slot);
|
|
121
|
+
const url = `https://wake.local/schedules/${encodeURIComponent(input.workflow)}/${encodeURIComponent(input.slot)}`;
|
|
122
|
+
return createUnkeyedEventEnvelope({
|
|
123
|
+
eventId,
|
|
124
|
+
streamScope: 'global-intake',
|
|
125
|
+
direction: 'inbound',
|
|
126
|
+
sourceSystem: 'wake',
|
|
127
|
+
sourceEventType: 'ticket.upsert',
|
|
128
|
+
sourceRefs: {
|
|
129
|
+
repo: syntheticRepo,
|
|
130
|
+
issueNumber: syntheticIssueNumber,
|
|
131
|
+
sourceUrl: url,
|
|
132
|
+
resourceUri,
|
|
133
|
+
},
|
|
134
|
+
occurredAt: input.slot,
|
|
135
|
+
ingestedAt: input.now,
|
|
136
|
+
trigger: 'immediate',
|
|
137
|
+
payload: {
|
|
138
|
+
ticket: {
|
|
139
|
+
repo: syntheticRepo,
|
|
140
|
+
number: syntheticIssueNumber,
|
|
141
|
+
title: `Scheduled workflow: ${input.workflow}`,
|
|
142
|
+
body: `Wake fired scheduled workflow "${input.workflow}" for cron slot ${input.slot}.`,
|
|
143
|
+
labels: ['wake:scheduled-workflow', `wake:workflow.${input.workflow}`],
|
|
144
|
+
assignees: [],
|
|
145
|
+
isPullRequest: false,
|
|
146
|
+
state: 'open',
|
|
147
|
+
url,
|
|
148
|
+
createdAt: input.slot,
|
|
149
|
+
updatedAt: input.slot,
|
|
150
|
+
},
|
|
151
|
+
workflow: input.workflow,
|
|
152
|
+
trigger: {
|
|
153
|
+
kind: 'schedule',
|
|
154
|
+
slot: input.slot,
|
|
155
|
+
idempotencyKey: eventId,
|
|
156
|
+
},
|
|
157
|
+
providerEventType: 'wake.schedule.fired',
|
|
158
|
+
},
|
|
159
|
+
derivedHints: {
|
|
160
|
+
workflow: input.workflow,
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
export function createScheduledWorkflowSource(deps) {
|
|
165
|
+
return {
|
|
166
|
+
async pollEvents() {
|
|
167
|
+
const events = [];
|
|
168
|
+
const now = deps.now();
|
|
169
|
+
const nowIso = now.toISOString();
|
|
170
|
+
for (const [workflow, definition] of Object.entries(deps.config.workflows)) {
|
|
171
|
+
const cron = definition.trigger?.schedule?.cron;
|
|
172
|
+
if (cron === undefined) {
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
const state = await readTriggerState(deps.config.paths.wakeRoot, workflow);
|
|
176
|
+
const slot = previousMatchingSlot({
|
|
177
|
+
cron,
|
|
178
|
+
now,
|
|
179
|
+
...(state?.lastFiredSlot === undefined ? {} : { after: state.lastFiredSlot }),
|
|
180
|
+
});
|
|
181
|
+
if (slot === null) {
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
const eventId = scheduledEventId(workflow, slot);
|
|
185
|
+
if ((await deps.stateStore.readEventEnvelope(eventId)) !== null) {
|
|
186
|
+
await writeJsonFile(triggerStateFile(deps.config.paths.wakeRoot, workflow), {
|
|
187
|
+
schemaVersion: 1,
|
|
188
|
+
workflow,
|
|
189
|
+
lastFiredSlot: slot,
|
|
190
|
+
updatedAt: nowIso,
|
|
191
|
+
});
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
events.push(syntheticTicket({ workflow, slot, now: nowIso }));
|
|
195
|
+
}
|
|
196
|
+
return events;
|
|
197
|
+
},
|
|
198
|
+
};
|
|
199
|
+
}
|
|
@@ -602,6 +602,14 @@ export function createTickRunner(deps) {
|
|
|
602
602
|
runId,
|
|
603
603
|
routing,
|
|
604
604
|
workspaceMode,
|
|
605
|
+
...(action === 'triage-assign'
|
|
606
|
+
? {
|
|
607
|
+
promptContextOverrides: {
|
|
608
|
+
triageCapacityAvailable: true,
|
|
609
|
+
triageWipCapDescription: 'Wake has no active running work item at triage start; do not assign more than one issue.',
|
|
610
|
+
},
|
|
611
|
+
}
|
|
612
|
+
: {}),
|
|
605
613
|
...(workspacePath === undefined ? {} : { workspacePath }),
|
|
606
614
|
...(mergeConflictDetected ? { mergeConflictDetected: true } : {}),
|
|
607
615
|
...(upstreamChanges === undefined ? {} : { upstreamChanges }),
|
|
@@ -375,11 +375,18 @@ const workflowStageSchema = stageRouteSchema.extend({
|
|
|
375
375
|
workspace: workflowWorkspaceSchema,
|
|
376
376
|
onDone: identifierSchema,
|
|
377
377
|
});
|
|
378
|
+
const workflowTriggerScheduleSchema = z.object({
|
|
379
|
+
cron: z.string().min(1),
|
|
380
|
+
});
|
|
381
|
+
const workflowTriggerSchema = z.object({
|
|
382
|
+
schedule: workflowTriggerScheduleSchema.optional(),
|
|
383
|
+
});
|
|
378
384
|
const customCommandSchema = stageRouteSchema.extend({
|
|
379
385
|
workspace: workflowWorkspaceSchema.default('read-only'),
|
|
380
386
|
});
|
|
381
387
|
const workflowDefinitionSchema = z.object({
|
|
382
388
|
entryStage: identifierSchema.optional(),
|
|
389
|
+
trigger: workflowTriggerSchema.optional(),
|
|
383
390
|
stages: z.record(identifierSchema, workflowStageSchema),
|
|
384
391
|
});
|
|
385
392
|
const workflowSelectorMatchSchema = z
|
|
@@ -605,6 +612,21 @@ const wakeConfigBaseSchema = z.object({
|
|
|
605
612
|
},
|
|
606
613
|
},
|
|
607
614
|
},
|
|
615
|
+
triage: {
|
|
616
|
+
trigger: {
|
|
617
|
+
schedule: {
|
|
618
|
+
cron: '*/10 * * * *',
|
|
619
|
+
},
|
|
620
|
+
},
|
|
621
|
+
stages: {
|
|
622
|
+
assign: {
|
|
623
|
+
action: 'triage-assign',
|
|
624
|
+
workspace: 'none',
|
|
625
|
+
tier: 'light',
|
|
626
|
+
onDone: 'done',
|
|
627
|
+
},
|
|
628
|
+
},
|
|
629
|
+
},
|
|
608
630
|
}),
|
|
609
631
|
workflowSelectors: z.array(workflowSelectorSchema).default([]),
|
|
610
632
|
commands: z.record(identifierSchema, customCommandSchema).default({
|
|
@@ -102,6 +102,10 @@ function selectorMatches(selector, input) {
|
|
|
102
102
|
return true;
|
|
103
103
|
}
|
|
104
104
|
export function selectWorkflowForEvent(event, config) {
|
|
105
|
+
const hintedWorkflow = event.payload.workflow;
|
|
106
|
+
if (typeof hintedWorkflow === 'string' && config.workflows[hintedWorkflow] !== undefined) {
|
|
107
|
+
return hintedWorkflow;
|
|
108
|
+
}
|
|
105
109
|
const input = workflowSelectorInputFromEvent(event);
|
|
106
110
|
const selector = config.workflowSelectors.find((candidate) => selectorMatches(candidate, input));
|
|
107
111
|
return selector?.workflow ?? null;
|
package/dist/src/main.js
CHANGED
|
@@ -31,6 +31,7 @@ import { loadWakeConfig } from './config/load-config.js';
|
|
|
31
31
|
import { createActiveRunRecovery } from './core/active-run-recovery.js';
|
|
32
32
|
import { createControlPlane } from './core/control-plane.js';
|
|
33
33
|
import { createOutboundSinkRouter, createWorkSourceFanIn } from './core/sink-router.js';
|
|
34
|
+
import { createScheduledWorkflowSource } from './core/scheduled-workflow-source.js';
|
|
34
35
|
import { createTickRunner } from './core/tick-runner.js';
|
|
35
36
|
import { systemClock } from './lib/clock.js';
|
|
36
37
|
import { createDetachedProcessLogSink } from './lib/detached-process-logging.js';
|
|
@@ -521,6 +522,14 @@ export async function buildRuntime(args) {
|
|
|
521
522
|
})
|
|
522
523
|
: null;
|
|
523
524
|
const workSource = createWorkSourceFanIn([
|
|
525
|
+
{
|
|
526
|
+
source: 'wake-schedule',
|
|
527
|
+
pollEvents: createScheduledWorkflowSource({
|
|
528
|
+
config,
|
|
529
|
+
stateStore,
|
|
530
|
+
now: () => systemClock.now(),
|
|
531
|
+
}).pollEvents,
|
|
532
|
+
},
|
|
524
533
|
{
|
|
525
534
|
source: sourceName,
|
|
526
535
|
pollEvents: ticketingSystem.pollEvents,
|
package/dist/src/version.js
CHANGED
package/package.json
CHANGED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
stage: assign
|
|
3
|
+
permissionMode: acceptEdits
|
|
4
|
+
allowedTools: Bash(gh *), Bash(jq *), Read
|
|
5
|
+
extraArgs:
|
|
6
|
+
maxTurns: 30
|
|
7
|
+
skipApproval: true
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
You are Wake, in the TRIAGE ASSIGN stage for {{workItemKey}}.
|
|
11
|
+
|
|
12
|
+
This stage may inspect the broad GitHub backlog and assign at most one issue to Wake.
|
|
13
|
+
{{toolCapabilityNote}}
|
|
14
|
+
|
|
15
|
+
Wake-side guardrails for this run:
|
|
16
|
+
|
|
17
|
+
- Capacity available: {{triageCapacityAvailable}}
|
|
18
|
+
- Do not assign more than one issue.
|
|
19
|
+
- Do not inspect or assign issues carrying any of these always-manual labels:
|
|
20
|
+
{{triageIgnoredLabelsJson}}
|
|
21
|
+
- Configured repositories:
|
|
22
|
+
{{triageReposJson}}
|
|
23
|
+
|
|
24
|
+
Use `gh issue list` and `gh issue view` only against the configured repositories.
|
|
25
|
+
Filter out every always-manual label in the GitHub query before viewing candidate
|
|
26
|
+
details. If no suitable issue remains, report DONE without assigning anything.
|
|
27
|
+
|
|
28
|
+
When you choose a candidate, assign it to the authenticated Wake GitHub user with
|
|
29
|
+
`gh issue edit <number> --repo <owner/repo> --add-assignee @me`.
|
|
30
|
+
|
|
31
|
+
Wake will provide the schedule trigger item below in a delimited untrusted data
|
|
32
|
+
block. It is an audit record, not the backlog to triage.
|
|
33
|
+
|