@renseiai/agentfactory 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +125 -0
- package/dist/src/config/index.d.ts +3 -0
- package/dist/src/config/index.d.ts.map +1 -0
- package/dist/src/config/index.js +1 -0
- package/dist/src/config/repository-config.d.ts +44 -0
- package/dist/src/config/repository-config.d.ts.map +1 -0
- package/dist/src/config/repository-config.js +88 -0
- package/dist/src/config/repository-config.test.d.ts +2 -0
- package/dist/src/config/repository-config.test.d.ts.map +1 -0
- package/dist/src/config/repository-config.test.js +249 -0
- package/dist/src/deployment/deployment-checker.d.ts +110 -0
- package/dist/src/deployment/deployment-checker.d.ts.map +1 -0
- package/dist/src/deployment/deployment-checker.js +242 -0
- package/dist/src/deployment/index.d.ts +3 -0
- package/dist/src/deployment/index.d.ts.map +1 -0
- package/dist/src/deployment/index.js +2 -0
- package/dist/src/frontend/index.d.ts +2 -0
- package/dist/src/frontend/index.d.ts.map +1 -0
- package/dist/src/frontend/index.js +1 -0
- package/dist/src/frontend/types.d.ts +106 -0
- package/dist/src/frontend/types.d.ts.map +1 -0
- package/dist/src/frontend/types.js +11 -0
- package/dist/src/governor/decision-engine.d.ts +52 -0
- package/dist/src/governor/decision-engine.d.ts.map +1 -0
- package/dist/src/governor/decision-engine.js +220 -0
- package/dist/src/governor/decision-engine.test.d.ts +2 -0
- package/dist/src/governor/decision-engine.test.d.ts.map +1 -0
- package/dist/src/governor/decision-engine.test.js +629 -0
- package/dist/src/governor/event-bus.d.ts +43 -0
- package/dist/src/governor/event-bus.d.ts.map +1 -0
- package/dist/src/governor/event-bus.js +8 -0
- package/dist/src/governor/event-deduplicator.d.ts +43 -0
- package/dist/src/governor/event-deduplicator.d.ts.map +1 -0
- package/dist/src/governor/event-deduplicator.js +53 -0
- package/dist/src/governor/event-driven-governor.d.ts +131 -0
- package/dist/src/governor/event-driven-governor.d.ts.map +1 -0
- package/dist/src/governor/event-driven-governor.js +379 -0
- package/dist/src/governor/event-driven-governor.test.d.ts +2 -0
- package/dist/src/governor/event-driven-governor.test.d.ts.map +1 -0
- package/dist/src/governor/event-driven-governor.test.js +673 -0
- package/dist/src/governor/event-types.d.ts +78 -0
- package/dist/src/governor/event-types.d.ts.map +1 -0
- package/dist/src/governor/event-types.js +32 -0
- package/dist/src/governor/governor-types.d.ts +82 -0
- package/dist/src/governor/governor-types.d.ts.map +1 -0
- package/dist/src/governor/governor-types.js +21 -0
- package/dist/src/governor/governor.d.ts +100 -0
- package/dist/src/governor/governor.d.ts.map +1 -0
- package/dist/src/governor/governor.js +262 -0
- package/dist/src/governor/governor.test.d.ts +2 -0
- package/dist/src/governor/governor.test.d.ts.map +1 -0
- package/dist/src/governor/governor.test.js +514 -0
- package/dist/src/governor/human-touchpoints.d.ts +131 -0
- package/dist/src/governor/human-touchpoints.d.ts.map +1 -0
- package/dist/src/governor/human-touchpoints.js +251 -0
- package/dist/src/governor/human-touchpoints.test.d.ts +2 -0
- package/dist/src/governor/human-touchpoints.test.d.ts.map +1 -0
- package/dist/src/governor/human-touchpoints.test.js +366 -0
- package/dist/src/governor/in-memory-event-bus.d.ts +29 -0
- package/dist/src/governor/in-memory-event-bus.d.ts.map +1 -0
- package/dist/src/governor/in-memory-event-bus.js +79 -0
- package/dist/src/governor/index.d.ts +14 -0
- package/dist/src/governor/index.d.ts.map +1 -0
- package/dist/src/governor/index.js +13 -0
- package/dist/src/governor/override-parser.d.ts +60 -0
- package/dist/src/governor/override-parser.d.ts.map +1 -0
- package/dist/src/governor/override-parser.js +98 -0
- package/dist/src/governor/override-parser.test.d.ts +2 -0
- package/dist/src/governor/override-parser.test.d.ts.map +1 -0
- package/dist/src/governor/override-parser.test.js +312 -0
- package/dist/src/governor/platform-adapter.d.ts +69 -0
- package/dist/src/governor/platform-adapter.d.ts.map +1 -0
- package/dist/src/governor/platform-adapter.js +11 -0
- package/dist/src/governor/processing-state.d.ts +66 -0
- package/dist/src/governor/processing-state.d.ts.map +1 -0
- package/dist/src/governor/processing-state.js +43 -0
- package/dist/src/governor/processing-state.test.d.ts +2 -0
- package/dist/src/governor/processing-state.test.d.ts.map +1 -0
- package/dist/src/governor/processing-state.test.js +96 -0
- package/dist/src/governor/top-of-funnel.d.ts +118 -0
- package/dist/src/governor/top-of-funnel.d.ts.map +1 -0
- package/dist/src/governor/top-of-funnel.js +168 -0
- package/dist/src/governor/top-of-funnel.test.d.ts +2 -0
- package/dist/src/governor/top-of-funnel.test.d.ts.map +1 -0
- package/dist/src/governor/top-of-funnel.test.js +331 -0
- package/dist/src/index.d.ts +11 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +10 -0
- package/dist/src/linear-cli.d.ts +38 -0
- package/dist/src/linear-cli.d.ts.map +1 -0
- package/dist/src/linear-cli.js +674 -0
- package/dist/src/logger.d.ts +117 -0
- package/dist/src/logger.d.ts.map +1 -0
- package/dist/src/logger.js +430 -0
- package/dist/src/manifest/generate.d.ts +20 -0
- package/dist/src/manifest/generate.d.ts.map +1 -0
- package/dist/src/manifest/generate.js +65 -0
- package/dist/src/manifest/index.d.ts +4 -0
- package/dist/src/manifest/index.d.ts.map +1 -0
- package/dist/src/manifest/index.js +2 -0
- package/dist/src/manifest/route-manifest.d.ts +34 -0
- package/dist/src/manifest/route-manifest.d.ts.map +1 -0
- package/dist/src/manifest/route-manifest.js +148 -0
- package/dist/src/orchestrator/activity-emitter.d.ts +119 -0
- package/dist/src/orchestrator/activity-emitter.d.ts.map +1 -0
- package/dist/src/orchestrator/activity-emitter.js +306 -0
- package/dist/src/orchestrator/api-activity-emitter.d.ts +167 -0
- package/dist/src/orchestrator/api-activity-emitter.d.ts.map +1 -0
- package/dist/src/orchestrator/api-activity-emitter.js +417 -0
- package/dist/src/orchestrator/heartbeat-writer.d.ts +57 -0
- package/dist/src/orchestrator/heartbeat-writer.d.ts.map +1 -0
- package/dist/src/orchestrator/heartbeat-writer.js +137 -0
- package/dist/src/orchestrator/index.d.ts +20 -0
- package/dist/src/orchestrator/index.d.ts.map +1 -0
- package/dist/src/orchestrator/index.js +22 -0
- package/dist/src/orchestrator/log-analyzer.d.ts +160 -0
- package/dist/src/orchestrator/log-analyzer.d.ts.map +1 -0
- package/dist/src/orchestrator/log-analyzer.js +572 -0
- package/dist/src/orchestrator/log-config.d.ts +39 -0
- package/dist/src/orchestrator/log-config.d.ts.map +1 -0
- package/dist/src/orchestrator/log-config.js +45 -0
- package/dist/src/orchestrator/orchestrator.d.ts +316 -0
- package/dist/src/orchestrator/orchestrator.d.ts.map +1 -0
- package/dist/src/orchestrator/orchestrator.js +3290 -0
- package/dist/src/orchestrator/parse-work-result.d.ts +16 -0
- package/dist/src/orchestrator/parse-work-result.d.ts.map +1 -0
- package/dist/src/orchestrator/parse-work-result.js +135 -0
- package/dist/src/orchestrator/parse-work-result.test.d.ts +2 -0
- package/dist/src/orchestrator/parse-work-result.test.d.ts.map +1 -0
- package/dist/src/orchestrator/parse-work-result.test.js +234 -0
- package/dist/src/orchestrator/progress-logger.d.ts +72 -0
- package/dist/src/orchestrator/progress-logger.d.ts.map +1 -0
- package/dist/src/orchestrator/progress-logger.js +135 -0
- package/dist/src/orchestrator/session-logger.d.ts +159 -0
- package/dist/src/orchestrator/session-logger.d.ts.map +1 -0
- package/dist/src/orchestrator/session-logger.js +275 -0
- package/dist/src/orchestrator/state-recovery.d.ts +96 -0
- package/dist/src/orchestrator/state-recovery.d.ts.map +1 -0
- package/dist/src/orchestrator/state-recovery.js +302 -0
- package/dist/src/orchestrator/state-types.d.ts +165 -0
- package/dist/src/orchestrator/state-types.d.ts.map +1 -0
- package/dist/src/orchestrator/state-types.js +7 -0
- package/dist/src/orchestrator/stream-parser.d.ts +151 -0
- package/dist/src/orchestrator/stream-parser.d.ts.map +1 -0
- package/dist/src/orchestrator/stream-parser.js +137 -0
- package/dist/src/orchestrator/types.d.ts +232 -0
- package/dist/src/orchestrator/types.d.ts.map +1 -0
- package/dist/src/orchestrator/types.js +4 -0
- package/dist/src/orchestrator/validate-git-remote.test.d.ts +2 -0
- package/dist/src/orchestrator/validate-git-remote.test.d.ts.map +1 -0
- package/dist/src/orchestrator/validate-git-remote.test.js +61 -0
- package/dist/src/providers/a2a-auth.d.ts +81 -0
- package/dist/src/providers/a2a-auth.d.ts.map +1 -0
- package/dist/src/providers/a2a-auth.js +188 -0
- package/dist/src/providers/a2a-auth.test.d.ts +2 -0
- package/dist/src/providers/a2a-auth.test.d.ts.map +1 -0
- package/dist/src/providers/a2a-auth.test.js +232 -0
- package/dist/src/providers/a2a-provider.d.ts +254 -0
- package/dist/src/providers/a2a-provider.d.ts.map +1 -0
- package/dist/src/providers/a2a-provider.integration.test.d.ts +9 -0
- package/dist/src/providers/a2a-provider.integration.test.d.ts.map +1 -0
- package/dist/src/providers/a2a-provider.integration.test.js +665 -0
- package/dist/src/providers/a2a-provider.js +811 -0
- package/dist/src/providers/a2a-provider.test.d.ts +2 -0
- package/dist/src/providers/a2a-provider.test.d.ts.map +1 -0
- package/dist/src/providers/a2a-provider.test.js +681 -0
- package/dist/src/providers/amp-provider.d.ts +20 -0
- package/dist/src/providers/amp-provider.d.ts.map +1 -0
- package/dist/src/providers/amp-provider.js +24 -0
- package/dist/src/providers/claude-provider.d.ts +18 -0
- package/dist/src/providers/claude-provider.d.ts.map +1 -0
- package/dist/src/providers/claude-provider.js +437 -0
- package/dist/src/providers/codex-provider.d.ts +133 -0
- package/dist/src/providers/codex-provider.d.ts.map +1 -0
- package/dist/src/providers/codex-provider.js +381 -0
- package/dist/src/providers/codex-provider.test.d.ts +2 -0
- package/dist/src/providers/codex-provider.test.d.ts.map +1 -0
- package/dist/src/providers/codex-provider.test.js +387 -0
- package/dist/src/providers/index.d.ts +44 -0
- package/dist/src/providers/index.d.ts.map +1 -0
- package/dist/src/providers/index.js +85 -0
- package/dist/src/providers/spring-ai-provider.d.ts +90 -0
- package/dist/src/providers/spring-ai-provider.d.ts.map +1 -0
- package/dist/src/providers/spring-ai-provider.integration.test.d.ts +13 -0
- package/dist/src/providers/spring-ai-provider.integration.test.d.ts.map +1 -0
- package/dist/src/providers/spring-ai-provider.integration.test.js +351 -0
- package/dist/src/providers/spring-ai-provider.js +317 -0
- package/dist/src/providers/spring-ai-provider.test.d.ts +2 -0
- package/dist/src/providers/spring-ai-provider.test.d.ts.map +1 -0
- package/dist/src/providers/spring-ai-provider.test.js +200 -0
- package/dist/src/providers/types.d.ts +165 -0
- package/dist/src/providers/types.d.ts.map +1 -0
- package/dist/src/providers/types.js +13 -0
- package/dist/src/templates/adapters.d.ts +51 -0
- package/dist/src/templates/adapters.d.ts.map +1 -0
- package/dist/src/templates/adapters.js +104 -0
- package/dist/src/templates/adapters.test.d.ts +2 -0
- package/dist/src/templates/adapters.test.d.ts.map +1 -0
- package/dist/src/templates/adapters.test.js +165 -0
- package/dist/src/templates/agent-definition.d.ts +85 -0
- package/dist/src/templates/agent-definition.d.ts.map +1 -0
- package/dist/src/templates/agent-definition.js +97 -0
- package/dist/src/templates/agent-definition.test.d.ts +2 -0
- package/dist/src/templates/agent-definition.test.d.ts.map +1 -0
- package/dist/src/templates/agent-definition.test.js +209 -0
- package/dist/src/templates/index.d.ts +14 -0
- package/dist/src/templates/index.d.ts.map +1 -0
- package/dist/src/templates/index.js +11 -0
- package/dist/src/templates/loader.d.ts +41 -0
- package/dist/src/templates/loader.d.ts.map +1 -0
- package/dist/src/templates/loader.js +114 -0
- package/dist/src/templates/registry.d.ts +80 -0
- package/dist/src/templates/registry.d.ts.map +1 -0
- package/dist/src/templates/registry.js +177 -0
- package/dist/src/templates/registry.test.d.ts +2 -0
- package/dist/src/templates/registry.test.d.ts.map +1 -0
- package/dist/src/templates/registry.test.js +198 -0
- package/dist/src/templates/renderer.d.ts +29 -0
- package/dist/src/templates/renderer.d.ts.map +1 -0
- package/dist/src/templates/renderer.js +35 -0
- package/dist/src/templates/strategy-templates.test.d.ts +2 -0
- package/dist/src/templates/strategy-templates.test.d.ts.map +1 -0
- package/dist/src/templates/strategy-templates.test.js +619 -0
- package/dist/src/templates/types.d.ts +233 -0
- package/dist/src/templates/types.d.ts.map +1 -0
- package/dist/src/templates/types.js +127 -0
- package/dist/src/templates/types.test.d.ts +2 -0
- package/dist/src/templates/types.test.d.ts.map +1 -0
- package/dist/src/templates/types.test.js +232 -0
- package/dist/src/tools/index.d.ts +6 -0
- package/dist/src/tools/index.d.ts.map +1 -0
- package/dist/src/tools/index.js +3 -0
- package/dist/src/tools/linear-runner.d.ts +34 -0
- package/dist/src/tools/linear-runner.d.ts.map +1 -0
- package/dist/src/tools/linear-runner.js +700 -0
- package/dist/src/tools/plugins/linear.d.ts +9 -0
- package/dist/src/tools/plugins/linear.d.ts.map +1 -0
- package/dist/src/tools/plugins/linear.js +138 -0
- package/dist/src/tools/registry.d.ts +9 -0
- package/dist/src/tools/registry.d.ts.map +1 -0
- package/dist/src/tools/registry.js +18 -0
- package/dist/src/tools/types.d.ts +18 -0
- package/dist/src/tools/types.d.ts.map +1 -0
- package/dist/src/tools/types.js +1 -0
- package/package.json +78 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-Memory Event Bus
|
|
3
|
+
*
|
|
4
|
+
* Simple event bus implementation for testing and single-process CLI usage.
|
|
5
|
+
* Events are stored in a queue and delivered via an async generator.
|
|
6
|
+
*/
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// InMemoryEventBus
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
export class InMemoryEventBus {
|
|
11
|
+
queue = [];
|
|
12
|
+
waiters = [];
|
|
13
|
+
closed = false;
|
|
14
|
+
idCounter = 0;
|
|
15
|
+
ackedIds = new Set();
|
|
16
|
+
async publish(event) {
|
|
17
|
+
if (this.closed) {
|
|
18
|
+
throw new Error('Event bus is closed');
|
|
19
|
+
}
|
|
20
|
+
const id = `mem-${++this.idCounter}`;
|
|
21
|
+
const item = { id, event };
|
|
22
|
+
// If a subscriber is waiting, deliver directly
|
|
23
|
+
const waiter = this.waiters.shift();
|
|
24
|
+
if (waiter) {
|
|
25
|
+
waiter(item);
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
this.queue.push(item);
|
|
29
|
+
}
|
|
30
|
+
return id;
|
|
31
|
+
}
|
|
32
|
+
async *subscribe() {
|
|
33
|
+
while (!this.closed) {
|
|
34
|
+
// Try to pull from the queue
|
|
35
|
+
const item = this.queue.shift();
|
|
36
|
+
if (item) {
|
|
37
|
+
yield item;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
// Wait for a new event or close
|
|
41
|
+
const next = await new Promise((resolve) => {
|
|
42
|
+
if (this.closed) {
|
|
43
|
+
resolve(null);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
this.waiters.push(resolve);
|
|
47
|
+
});
|
|
48
|
+
if (next === null) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
yield next;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async ack(eventId) {
|
|
55
|
+
this.ackedIds.add(eventId);
|
|
56
|
+
}
|
|
57
|
+
async close() {
|
|
58
|
+
this.closed = true;
|
|
59
|
+
// Resolve all waiting subscribers with null to end iteration
|
|
60
|
+
for (const waiter of this.waiters) {
|
|
61
|
+
;
|
|
62
|
+
waiter(null);
|
|
63
|
+
}
|
|
64
|
+
this.waiters = [];
|
|
65
|
+
}
|
|
66
|
+
// ---- Test helpers ----
|
|
67
|
+
/** Check if an event ID has been acknowledged */
|
|
68
|
+
isAcked(eventId) {
|
|
69
|
+
return this.ackedIds.has(eventId);
|
|
70
|
+
}
|
|
71
|
+
/** Get the number of pending (undelivered) events */
|
|
72
|
+
get pendingCount() {
|
|
73
|
+
return this.queue.length;
|
|
74
|
+
}
|
|
75
|
+
/** Check if the bus has been closed */
|
|
76
|
+
get isClosed() {
|
|
77
|
+
return this.closed;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export * from './override-parser.js';
|
|
2
|
+
export * from './human-touchpoints.js';
|
|
3
|
+
export * from './top-of-funnel.js';
|
|
4
|
+
export * from './processing-state.js';
|
|
5
|
+
export * from './governor-types.js';
|
|
6
|
+
export * from './decision-engine.js';
|
|
7
|
+
export * from './governor.js';
|
|
8
|
+
export * from './event-types.js';
|
|
9
|
+
export * from './event-bus.js';
|
|
10
|
+
export * from './in-memory-event-bus.js';
|
|
11
|
+
export * from './event-deduplicator.js';
|
|
12
|
+
export * from './event-driven-governor.js';
|
|
13
|
+
export * from './platform-adapter.js';
|
|
14
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/governor/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAA;AACpC,cAAc,wBAAwB,CAAA;AACtC,cAAc,oBAAoB,CAAA;AAClC,cAAc,uBAAuB,CAAA;AACrC,cAAc,qBAAqB,CAAA;AACnC,cAAc,sBAAsB,CAAA;AACpC,cAAc,eAAe,CAAA;AAC7B,cAAc,kBAAkB,CAAA;AAChC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,0BAA0B,CAAA;AACxC,cAAc,yBAAyB,CAAA;AACvC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,uBAAuB,CAAA"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export * from './override-parser.js';
|
|
2
|
+
export * from './human-touchpoints.js';
|
|
3
|
+
export * from './top-of-funnel.js';
|
|
4
|
+
export * from './processing-state.js';
|
|
5
|
+
export * from './governor-types.js';
|
|
6
|
+
export * from './decision-engine.js';
|
|
7
|
+
export * from './governor.js';
|
|
8
|
+
export * from './event-types.js';
|
|
9
|
+
export * from './event-bus.js';
|
|
10
|
+
export * from './in-memory-event-bus.js';
|
|
11
|
+
export * from './event-deduplicator.js';
|
|
12
|
+
export * from './event-driven-governor.js';
|
|
13
|
+
export * from './platform-adapter.js';
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Override Parser Module
|
|
3
|
+
*
|
|
4
|
+
* Parses issue comments for structured human override directives.
|
|
5
|
+
* Directives must appear at the start of a comment (case-insensitive)
|
|
6
|
+
* and only non-bot comments are processed.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Supported override directive types
|
|
10
|
+
*/
|
|
11
|
+
export type OverrideDirectiveType = 'hold' | 'resume' | 'skip-qa' | 'decompose' | 'reassign' | 'priority';
|
|
12
|
+
/**
|
|
13
|
+
* Priority levels for the PRIORITY directive
|
|
14
|
+
*/
|
|
15
|
+
export type OverridePriority = 'high' | 'medium' | 'low';
|
|
16
|
+
/**
|
|
17
|
+
* A parsed override directive extracted from a comment
|
|
18
|
+
*/
|
|
19
|
+
export interface OverrideDirective {
|
|
20
|
+
type: OverrideDirectiveType;
|
|
21
|
+
reason?: string;
|
|
22
|
+
priority?: OverridePriority;
|
|
23
|
+
commentId?: string;
|
|
24
|
+
userId?: string;
|
|
25
|
+
timestamp: number;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Information about a comment to be parsed for directives
|
|
29
|
+
*/
|
|
30
|
+
export interface CommentInfo {
|
|
31
|
+
id: string;
|
|
32
|
+
body: string;
|
|
33
|
+
userId: string;
|
|
34
|
+
isBot: boolean;
|
|
35
|
+
createdAt: number;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Parse a single comment for an override directive.
|
|
39
|
+
*
|
|
40
|
+
* Rules:
|
|
41
|
+
* - Bot comments are always ignored (returns null)
|
|
42
|
+
* - Only the first line of the comment is checked for a directive
|
|
43
|
+
* - Directives are case-insensitive
|
|
44
|
+
* - Returns null if no directive is found
|
|
45
|
+
*
|
|
46
|
+
* @param comment - The comment to parse
|
|
47
|
+
* @returns The parsed directive, or null if no directive found
|
|
48
|
+
*/
|
|
49
|
+
export declare function parseOverrideDirective(comment: CommentInfo): OverrideDirective | null;
|
|
50
|
+
/**
|
|
51
|
+
* Find the most recent override directive from a list of comments.
|
|
52
|
+
*
|
|
53
|
+
* Parses all comments (skipping bots) and returns the directive with
|
|
54
|
+
* the latest timestamp. If no directive is found, returns null.
|
|
55
|
+
*
|
|
56
|
+
* @param comments - Array of comments to scan, in any order
|
|
57
|
+
* @returns The most recent directive, or null if none found
|
|
58
|
+
*/
|
|
59
|
+
export declare function findLatestOverride(comments: CommentInfo[]): OverrideDirective | null;
|
|
60
|
+
//# sourceMappingURL=override-parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"override-parser.d.ts","sourceRoot":"","sources":["../../../src/governor/override-parser.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH;;GAEG;AACH,MAAM,MAAM,qBAAqB,GAAG,MAAM,GAAG,QAAQ,GAAG,SAAS,GAAG,WAAW,GAAG,UAAU,GAAG,UAAU,CAAA;AAEzG;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAA;AAExD;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,qBAAqB,CAAA;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,gBAAgB,CAAA;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,OAAO,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;CAClB;AAkCD;;;;;;;;;;;GAWG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,WAAW,GAAG,iBAAiB,GAAG,IAAI,CAqCrF;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,WAAW,EAAE,GAAG,iBAAiB,GAAG,IAAI,CAapF"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Override Parser Module
|
|
3
|
+
*
|
|
4
|
+
* Parses issue comments for structured human override directives.
|
|
5
|
+
* Directives must appear at the start of a comment (case-insensitive)
|
|
6
|
+
* and only non-bot comments are processed.
|
|
7
|
+
*/
|
|
8
|
+
// ============================================
|
|
9
|
+
// Directive Patterns
|
|
10
|
+
// ============================================
|
|
11
|
+
/**
|
|
12
|
+
* Regex patterns for each directive type.
|
|
13
|
+
* All patterns are anchored to the start of the comment body (after trimming).
|
|
14
|
+
* The HOLD pattern captures an optional reason after a dash or em-dash.
|
|
15
|
+
* The PRIORITY pattern captures the priority level.
|
|
16
|
+
*/
|
|
17
|
+
const DIRECTIVE_PATTERNS = [
|
|
18
|
+
// HOLD or HOLD — reason or HOLD - reason
|
|
19
|
+
{ type: 'hold', pattern: /^hold(?:\s*[—–-]\s*(.+))?$/i },
|
|
20
|
+
// RESUME
|
|
21
|
+
{ type: 'resume', pattern: /^resume$/i },
|
|
22
|
+
// SKIP QA or SKIP-QA
|
|
23
|
+
{ type: 'skip-qa', pattern: /^skip[\s-]+qa$/i },
|
|
24
|
+
// DECOMPOSE
|
|
25
|
+
{ type: 'decompose', pattern: /^decompose$/i },
|
|
26
|
+
// REASSIGN
|
|
27
|
+
{ type: 'reassign', pattern: /^reassign$/i },
|
|
28
|
+
// PRIORITY: high|medium|low
|
|
29
|
+
{ type: 'priority', pattern: /^priority:\s*(high|medium|low)$/i },
|
|
30
|
+
];
|
|
31
|
+
// ============================================
|
|
32
|
+
// Parser Functions
|
|
33
|
+
// ============================================
|
|
34
|
+
/**
|
|
35
|
+
* Parse a single comment for an override directive.
|
|
36
|
+
*
|
|
37
|
+
* Rules:
|
|
38
|
+
* - Bot comments are always ignored (returns null)
|
|
39
|
+
* - Only the first line of the comment is checked for a directive
|
|
40
|
+
* - Directives are case-insensitive
|
|
41
|
+
* - Returns null if no directive is found
|
|
42
|
+
*
|
|
43
|
+
* @param comment - The comment to parse
|
|
44
|
+
* @returns The parsed directive, or null if no directive found
|
|
45
|
+
*/
|
|
46
|
+
export function parseOverrideDirective(comment) {
|
|
47
|
+
// Ignore bot comments
|
|
48
|
+
if (comment.isBot) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
// Take the first line of the comment body, trimmed
|
|
52
|
+
const firstLine = comment.body.trim().split('\n')[0]?.trim();
|
|
53
|
+
if (!firstLine) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
for (const { type, pattern } of DIRECTIVE_PATTERNS) {
|
|
57
|
+
const match = firstLine.match(pattern);
|
|
58
|
+
if (match) {
|
|
59
|
+
const directive = {
|
|
60
|
+
type,
|
|
61
|
+
commentId: comment.id,
|
|
62
|
+
userId: comment.userId,
|
|
63
|
+
timestamp: comment.createdAt,
|
|
64
|
+
};
|
|
65
|
+
// Extract reason for HOLD directive
|
|
66
|
+
if (type === 'hold' && match[1]) {
|
|
67
|
+
directive.reason = match[1].trim();
|
|
68
|
+
}
|
|
69
|
+
// Extract priority level for PRIORITY directive
|
|
70
|
+
if (type === 'priority' && match[1]) {
|
|
71
|
+
directive.priority = match[1].toLowerCase();
|
|
72
|
+
}
|
|
73
|
+
return directive;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Find the most recent override directive from a list of comments.
|
|
80
|
+
*
|
|
81
|
+
* Parses all comments (skipping bots) and returns the directive with
|
|
82
|
+
* the latest timestamp. If no directive is found, returns null.
|
|
83
|
+
*
|
|
84
|
+
* @param comments - Array of comments to scan, in any order
|
|
85
|
+
* @returns The most recent directive, or null if none found
|
|
86
|
+
*/
|
|
87
|
+
export function findLatestOverride(comments) {
|
|
88
|
+
let latest = null;
|
|
89
|
+
for (const comment of comments) {
|
|
90
|
+
const directive = parseOverrideDirective(comment);
|
|
91
|
+
if (directive) {
|
|
92
|
+
if (!latest || directive.timestamp > latest.timestamp) {
|
|
93
|
+
latest = directive;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return latest;
|
|
98
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"override-parser.test.d.ts","sourceRoot":"","sources":["../../../src/governor/override-parser.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { parseOverrideDirective, findLatestOverride } from './override-parser.js';
|
|
3
|
+
// ============================================
|
|
4
|
+
// Helpers
|
|
5
|
+
// ============================================
|
|
6
|
+
function makeComment(overrides = {}) {
|
|
7
|
+
return {
|
|
8
|
+
id: 'comment-1',
|
|
9
|
+
body: '',
|
|
10
|
+
userId: 'user-1',
|
|
11
|
+
isBot: false,
|
|
12
|
+
createdAt: Date.now(),
|
|
13
|
+
...overrides,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
// ============================================
|
|
17
|
+
// Tests
|
|
18
|
+
// ============================================
|
|
19
|
+
describe('parseOverrideDirective', () => {
|
|
20
|
+
describe('HOLD directive', () => {
|
|
21
|
+
it('parses plain HOLD', () => {
|
|
22
|
+
const comment = makeComment({ body: 'HOLD' });
|
|
23
|
+
const result = parseOverrideDirective(comment);
|
|
24
|
+
expect(result).not.toBeNull();
|
|
25
|
+
expect(result.type).toBe('hold');
|
|
26
|
+
expect(result.reason).toBeUndefined();
|
|
27
|
+
});
|
|
28
|
+
it('parses HOLD with em-dash reason', () => {
|
|
29
|
+
const comment = makeComment({ body: 'HOLD — waiting for design review' });
|
|
30
|
+
const result = parseOverrideDirective(comment);
|
|
31
|
+
expect(result).not.toBeNull();
|
|
32
|
+
expect(result.type).toBe('hold');
|
|
33
|
+
expect(result.reason).toBe('waiting for design review');
|
|
34
|
+
});
|
|
35
|
+
it('parses HOLD with regular dash reason', () => {
|
|
36
|
+
const comment = makeComment({ body: 'HOLD - need more info' });
|
|
37
|
+
const result = parseOverrideDirective(comment);
|
|
38
|
+
expect(result).not.toBeNull();
|
|
39
|
+
expect(result.type).toBe('hold');
|
|
40
|
+
expect(result.reason).toBe('need more info');
|
|
41
|
+
});
|
|
42
|
+
it('parses HOLD with en-dash reason', () => {
|
|
43
|
+
const comment = makeComment({ body: 'HOLD – performance concerns' });
|
|
44
|
+
const result = parseOverrideDirective(comment);
|
|
45
|
+
expect(result).not.toBeNull();
|
|
46
|
+
expect(result.type).toBe('hold');
|
|
47
|
+
expect(result.reason).toBe('performance concerns');
|
|
48
|
+
});
|
|
49
|
+
it('parses HOLD case-insensitively', () => {
|
|
50
|
+
const comment = makeComment({ body: 'hold' });
|
|
51
|
+
const result = parseOverrideDirective(comment);
|
|
52
|
+
expect(result).not.toBeNull();
|
|
53
|
+
expect(result.type).toBe('hold');
|
|
54
|
+
});
|
|
55
|
+
it('parses Hold (mixed case)', () => {
|
|
56
|
+
const comment = makeComment({ body: 'Hold — mixed case' });
|
|
57
|
+
const result = parseOverrideDirective(comment);
|
|
58
|
+
expect(result).not.toBeNull();
|
|
59
|
+
expect(result.type).toBe('hold');
|
|
60
|
+
expect(result.reason).toBe('mixed case');
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
describe('RESUME directive', () => {
|
|
64
|
+
it('parses RESUME', () => {
|
|
65
|
+
const comment = makeComment({ body: 'RESUME' });
|
|
66
|
+
const result = parseOverrideDirective(comment);
|
|
67
|
+
expect(result).not.toBeNull();
|
|
68
|
+
expect(result.type).toBe('resume');
|
|
69
|
+
});
|
|
70
|
+
it('parses resume (lowercase)', () => {
|
|
71
|
+
const comment = makeComment({ body: 'resume' });
|
|
72
|
+
const result = parseOverrideDirective(comment);
|
|
73
|
+
expect(result).not.toBeNull();
|
|
74
|
+
expect(result.type).toBe('resume');
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
describe('SKIP QA directive', () => {
|
|
78
|
+
it('parses SKIP QA', () => {
|
|
79
|
+
const comment = makeComment({ body: 'SKIP QA' });
|
|
80
|
+
const result = parseOverrideDirective(comment);
|
|
81
|
+
expect(result).not.toBeNull();
|
|
82
|
+
expect(result.type).toBe('skip-qa');
|
|
83
|
+
});
|
|
84
|
+
it('parses skip qa (lowercase)', () => {
|
|
85
|
+
const comment = makeComment({ body: 'skip qa' });
|
|
86
|
+
const result = parseOverrideDirective(comment);
|
|
87
|
+
expect(result).not.toBeNull();
|
|
88
|
+
expect(result.type).toBe('skip-qa');
|
|
89
|
+
});
|
|
90
|
+
it('handles extra whitespace between SKIP and QA', () => {
|
|
91
|
+
const comment = makeComment({ body: 'SKIP QA' });
|
|
92
|
+
const result = parseOverrideDirective(comment);
|
|
93
|
+
expect(result).not.toBeNull();
|
|
94
|
+
expect(result.type).toBe('skip-qa');
|
|
95
|
+
});
|
|
96
|
+
it('parses SKIP-QA (hyphenated)', () => {
|
|
97
|
+
const comment = makeComment({ body: 'SKIP-QA' });
|
|
98
|
+
const result = parseOverrideDirective(comment);
|
|
99
|
+
expect(result).not.toBeNull();
|
|
100
|
+
expect(result.type).toBe('skip-qa');
|
|
101
|
+
});
|
|
102
|
+
it('parses skip-qa (hyphenated lowercase)', () => {
|
|
103
|
+
const comment = makeComment({ body: 'skip-qa' });
|
|
104
|
+
const result = parseOverrideDirective(comment);
|
|
105
|
+
expect(result).not.toBeNull();
|
|
106
|
+
expect(result.type).toBe('skip-qa');
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
describe('DECOMPOSE directive', () => {
|
|
110
|
+
it('parses DECOMPOSE', () => {
|
|
111
|
+
const comment = makeComment({ body: 'DECOMPOSE' });
|
|
112
|
+
const result = parseOverrideDirective(comment);
|
|
113
|
+
expect(result).not.toBeNull();
|
|
114
|
+
expect(result.type).toBe('decompose');
|
|
115
|
+
});
|
|
116
|
+
it('parses decompose (lowercase)', () => {
|
|
117
|
+
const comment = makeComment({ body: 'decompose' });
|
|
118
|
+
const result = parseOverrideDirective(comment);
|
|
119
|
+
expect(result).not.toBeNull();
|
|
120
|
+
expect(result.type).toBe('decompose');
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
describe('REASSIGN directive', () => {
|
|
124
|
+
it('parses REASSIGN', () => {
|
|
125
|
+
const comment = makeComment({ body: 'REASSIGN' });
|
|
126
|
+
const result = parseOverrideDirective(comment);
|
|
127
|
+
expect(result).not.toBeNull();
|
|
128
|
+
expect(result.type).toBe('reassign');
|
|
129
|
+
});
|
|
130
|
+
it('parses Reassign (mixed case)', () => {
|
|
131
|
+
const comment = makeComment({ body: 'Reassign' });
|
|
132
|
+
const result = parseOverrideDirective(comment);
|
|
133
|
+
expect(result).not.toBeNull();
|
|
134
|
+
expect(result.type).toBe('reassign');
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
describe('PRIORITY directive', () => {
|
|
138
|
+
it('parses PRIORITY: high', () => {
|
|
139
|
+
const comment = makeComment({ body: 'PRIORITY: high' });
|
|
140
|
+
const result = parseOverrideDirective(comment);
|
|
141
|
+
expect(result).not.toBeNull();
|
|
142
|
+
expect(result.type).toBe('priority');
|
|
143
|
+
expect(result.priority).toBe('high');
|
|
144
|
+
});
|
|
145
|
+
it('parses PRIORITY: medium', () => {
|
|
146
|
+
const comment = makeComment({ body: 'PRIORITY: medium' });
|
|
147
|
+
const result = parseOverrideDirective(comment);
|
|
148
|
+
expect(result).not.toBeNull();
|
|
149
|
+
expect(result.type).toBe('priority');
|
|
150
|
+
expect(result.priority).toBe('medium');
|
|
151
|
+
});
|
|
152
|
+
it('parses PRIORITY: low', () => {
|
|
153
|
+
const comment = makeComment({ body: 'PRIORITY: low' });
|
|
154
|
+
const result = parseOverrideDirective(comment);
|
|
155
|
+
expect(result).not.toBeNull();
|
|
156
|
+
expect(result.type).toBe('priority');
|
|
157
|
+
expect(result.priority).toBe('low');
|
|
158
|
+
});
|
|
159
|
+
it('parses priority case-insensitively', () => {
|
|
160
|
+
const comment = makeComment({ body: 'priority: HIGH' });
|
|
161
|
+
const result = parseOverrideDirective(comment);
|
|
162
|
+
expect(result).not.toBeNull();
|
|
163
|
+
expect(result.type).toBe('priority');
|
|
164
|
+
expect(result.priority).toBe('high');
|
|
165
|
+
});
|
|
166
|
+
it('handles extra whitespace after colon', () => {
|
|
167
|
+
const comment = makeComment({ body: 'PRIORITY: high' });
|
|
168
|
+
const result = parseOverrideDirective(comment);
|
|
169
|
+
expect(result).not.toBeNull();
|
|
170
|
+
expect(result.type).toBe('priority');
|
|
171
|
+
expect(result.priority).toBe('high');
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
describe('bot filtering', () => {
|
|
175
|
+
it('ignores bot comments', () => {
|
|
176
|
+
const comment = makeComment({ body: 'HOLD', isBot: true });
|
|
177
|
+
const result = parseOverrideDirective(comment);
|
|
178
|
+
expect(result).toBeNull();
|
|
179
|
+
});
|
|
180
|
+
it('ignores bot comments for all directive types', () => {
|
|
181
|
+
const directives = ['HOLD', 'RESUME', 'SKIP QA', 'DECOMPOSE', 'REASSIGN', 'PRIORITY: high'];
|
|
182
|
+
for (const body of directives) {
|
|
183
|
+
const comment = makeComment({ body, isBot: true });
|
|
184
|
+
const result = parseOverrideDirective(comment);
|
|
185
|
+
expect(result).toBeNull();
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
describe('no directive found', () => {
|
|
190
|
+
it('returns null for empty body', () => {
|
|
191
|
+
const comment = makeComment({ body: '' });
|
|
192
|
+
expect(parseOverrideDirective(comment)).toBeNull();
|
|
193
|
+
});
|
|
194
|
+
it('returns null for regular comment text', () => {
|
|
195
|
+
const comment = makeComment({ body: 'This looks good, nice work!' });
|
|
196
|
+
expect(parseOverrideDirective(comment)).toBeNull();
|
|
197
|
+
});
|
|
198
|
+
it('returns null for directive-like text not at start', () => {
|
|
199
|
+
const comment = makeComment({ body: 'I think we should HOLD on this' });
|
|
200
|
+
expect(parseOverrideDirective(comment)).toBeNull();
|
|
201
|
+
});
|
|
202
|
+
it('returns null for unsupported directives', () => {
|
|
203
|
+
const comment = makeComment({ body: 'CANCEL' });
|
|
204
|
+
expect(parseOverrideDirective(comment)).toBeNull();
|
|
205
|
+
});
|
|
206
|
+
it('returns null for PRIORITY with invalid level', () => {
|
|
207
|
+
const comment = makeComment({ body: 'PRIORITY: urgent' });
|
|
208
|
+
expect(parseOverrideDirective(comment)).toBeNull();
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
describe('metadata extraction', () => {
|
|
212
|
+
it('captures commentId and userId', () => {
|
|
213
|
+
const comment = makeComment({
|
|
214
|
+
id: 'comment-42',
|
|
215
|
+
body: 'HOLD',
|
|
216
|
+
userId: 'user-99',
|
|
217
|
+
createdAt: 1700000000000,
|
|
218
|
+
});
|
|
219
|
+
const result = parseOverrideDirective(comment);
|
|
220
|
+
expect(result).not.toBeNull();
|
|
221
|
+
expect(result.commentId).toBe('comment-42');
|
|
222
|
+
expect(result.userId).toBe('user-99');
|
|
223
|
+
expect(result.timestamp).toBe(1700000000000);
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
describe('multiline comments', () => {
|
|
227
|
+
it('only checks the first line for directives', () => {
|
|
228
|
+
const comment = makeComment({
|
|
229
|
+
body: 'HOLD — security concern\nThis needs review from the security team.\nPlease check the auth flow.',
|
|
230
|
+
});
|
|
231
|
+
const result = parseOverrideDirective(comment);
|
|
232
|
+
expect(result).not.toBeNull();
|
|
233
|
+
expect(result.type).toBe('hold');
|
|
234
|
+
expect(result.reason).toBe('security concern');
|
|
235
|
+
});
|
|
236
|
+
it('ignores directives on subsequent lines', () => {
|
|
237
|
+
const comment = makeComment({
|
|
238
|
+
body: 'Great work on this!\nHOLD\nNot really.',
|
|
239
|
+
});
|
|
240
|
+
expect(parseOverrideDirective(comment)).toBeNull();
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
describe('whitespace handling', () => {
|
|
244
|
+
it('trims leading whitespace from body', () => {
|
|
245
|
+
const comment = makeComment({ body: ' HOLD ' });
|
|
246
|
+
const result = parseOverrideDirective(comment);
|
|
247
|
+
expect(result).not.toBeNull();
|
|
248
|
+
expect(result.type).toBe('hold');
|
|
249
|
+
});
|
|
250
|
+
it('trims whitespace-only body', () => {
|
|
251
|
+
const comment = makeComment({ body: ' ' });
|
|
252
|
+
expect(parseOverrideDirective(comment)).toBeNull();
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
describe('findLatestOverride', () => {
|
|
257
|
+
it('returns null for empty comment list', () => {
|
|
258
|
+
expect(findLatestOverride([])).toBeNull();
|
|
259
|
+
});
|
|
260
|
+
it('returns null when no comments contain directives', () => {
|
|
261
|
+
const comments = [
|
|
262
|
+
makeComment({ body: 'Looks good!' }),
|
|
263
|
+
makeComment({ body: 'Nice work' }),
|
|
264
|
+
];
|
|
265
|
+
expect(findLatestOverride(comments)).toBeNull();
|
|
266
|
+
});
|
|
267
|
+
it('returns the only directive found', () => {
|
|
268
|
+
const comments = [
|
|
269
|
+
makeComment({ body: 'Looks good!', createdAt: 1000 }),
|
|
270
|
+
makeComment({ body: 'HOLD', createdAt: 2000 }),
|
|
271
|
+
makeComment({ body: 'What about tests?', createdAt: 3000 }),
|
|
272
|
+
];
|
|
273
|
+
const result = findLatestOverride(comments);
|
|
274
|
+
expect(result).not.toBeNull();
|
|
275
|
+
expect(result.type).toBe('hold');
|
|
276
|
+
expect(result.timestamp).toBe(2000);
|
|
277
|
+
});
|
|
278
|
+
it('returns the most recent directive when multiple exist', () => {
|
|
279
|
+
const comments = [
|
|
280
|
+
makeComment({ id: 'c1', body: 'HOLD', createdAt: 1000 }),
|
|
281
|
+
makeComment({ id: 'c2', body: 'RESUME', createdAt: 2000 }),
|
|
282
|
+
makeComment({ id: 'c3', body: 'HOLD — second hold', createdAt: 3000 }),
|
|
283
|
+
];
|
|
284
|
+
const result = findLatestOverride(comments);
|
|
285
|
+
expect(result).not.toBeNull();
|
|
286
|
+
expect(result.type).toBe('hold');
|
|
287
|
+
expect(result.reason).toBe('second hold');
|
|
288
|
+
expect(result.timestamp).toBe(3000);
|
|
289
|
+
});
|
|
290
|
+
it('returns the most recent directive regardless of order', () => {
|
|
291
|
+
// Comments may be passed in any order
|
|
292
|
+
const comments = [
|
|
293
|
+
makeComment({ id: 'c3', body: 'DECOMPOSE', createdAt: 3000 }),
|
|
294
|
+
makeComment({ id: 'c1', body: 'HOLD', createdAt: 1000 }),
|
|
295
|
+
makeComment({ id: 'c2', body: 'RESUME', createdAt: 5000 }),
|
|
296
|
+
];
|
|
297
|
+
const result = findLatestOverride(comments);
|
|
298
|
+
expect(result).not.toBeNull();
|
|
299
|
+
expect(result.type).toBe('resume');
|
|
300
|
+
expect(result.timestamp).toBe(5000);
|
|
301
|
+
});
|
|
302
|
+
it('skips bot comments when finding latest', () => {
|
|
303
|
+
const comments = [
|
|
304
|
+
makeComment({ body: 'HOLD', createdAt: 1000, isBot: false }),
|
|
305
|
+
makeComment({ body: 'RESUME', createdAt: 2000, isBot: true }),
|
|
306
|
+
];
|
|
307
|
+
const result = findLatestOverride(comments);
|
|
308
|
+
expect(result).not.toBeNull();
|
|
309
|
+
expect(result.type).toBe('hold');
|
|
310
|
+
expect(result.timestamp).toBe(1000);
|
|
311
|
+
});
|
|
312
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PlatformAdapter Interface
|
|
3
|
+
*
|
|
4
|
+
* Extends the WorkSchedulingFrontend concept with methods needed by the
|
|
5
|
+
* EventDrivenGovernor to process webhook events and poll project state.
|
|
6
|
+
*
|
|
7
|
+
* Platform adapters (e.g., LinearPlatformAdapter) implement this interface
|
|
8
|
+
* structurally, translating between platform-native concepts and the
|
|
9
|
+
* Governor's abstract event/issue types.
|
|
10
|
+
*/
|
|
11
|
+
import type { GovernorEvent } from './event-types.js';
|
|
12
|
+
import type { GovernorIssue } from './governor-types.js';
|
|
13
|
+
/**
|
|
14
|
+
* Adapter that bridges a project-management platform (Linear, Jira, etc.)
|
|
15
|
+
* to the Governor's event-driven processing model.
|
|
16
|
+
*
|
|
17
|
+
* Responsibilities:
|
|
18
|
+
* - Convert platform webhook payloads into GovernorEvents
|
|
19
|
+
* - Scan a project for all non-terminal issues (poll sweep)
|
|
20
|
+
* - Convert platform-native issue objects to GovernorIssue
|
|
21
|
+
* - Detect parent/child issue relationships
|
|
22
|
+
*/
|
|
23
|
+
export interface PlatformAdapter {
|
|
24
|
+
/** Human-readable name of the platform (e.g., 'linear', 'jira'). */
|
|
25
|
+
readonly name: string;
|
|
26
|
+
/**
|
|
27
|
+
* Normalize a raw webhook payload from the platform into one or more
|
|
28
|
+
* GovernorEvents.
|
|
29
|
+
*
|
|
30
|
+
* Returns `null` if the payload type is not recognized or not relevant
|
|
31
|
+
* to the Governor (e.g., label-only changes, unrelated resource types).
|
|
32
|
+
*
|
|
33
|
+
* @param payload - Raw webhook payload from the platform
|
|
34
|
+
* @returns Array of GovernorEvents, or null if unrecognized
|
|
35
|
+
*/
|
|
36
|
+
normalizeWebhookEvent(payload: unknown): GovernorEvent[] | null;
|
|
37
|
+
/**
|
|
38
|
+
* Fetch all non-terminal issues for the given project.
|
|
39
|
+
*
|
|
40
|
+
* Terminal statuses (e.g., Accepted, Canceled, Duplicate) are excluded
|
|
41
|
+
* so the Governor only evaluates issues that may need action.
|
|
42
|
+
*
|
|
43
|
+
* @param project - Project name to scan
|
|
44
|
+
* @returns Array of GovernorIssue representing active issues
|
|
45
|
+
*/
|
|
46
|
+
scanProjectIssues(project: string): Promise<GovernorIssue[]>;
|
|
47
|
+
/**
|
|
48
|
+
* Convert a platform-native issue object to a GovernorIssue.
|
|
49
|
+
*
|
|
50
|
+
* The `native` parameter is typed as `unknown` to keep the interface
|
|
51
|
+
* platform-agnostic. Implementations should cast to the appropriate
|
|
52
|
+
* platform SDK type internally.
|
|
53
|
+
*
|
|
54
|
+
* @param native - Platform-native issue object
|
|
55
|
+
* @returns GovernorIssue representation
|
|
56
|
+
*/
|
|
57
|
+
toGovernorIssue(native: unknown): Promise<GovernorIssue>;
|
|
58
|
+
/**
|
|
59
|
+
* Check whether the given issue is a parent issue (has child/sub-issues).
|
|
60
|
+
*
|
|
61
|
+
* Parent issues receive special treatment in the Governor: they are
|
|
62
|
+
* typically managed by a coordinator rather than dispatched directly.
|
|
63
|
+
*
|
|
64
|
+
* @param issueId - Issue ID to check
|
|
65
|
+
* @returns True if the issue has child issues
|
|
66
|
+
*/
|
|
67
|
+
isParentIssue(issueId: string): Promise<boolean>;
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=platform-adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"platform-adapter.d.ts","sourceRoot":"","sources":["../../../src/governor/platform-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AACrD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAMxD;;;;;;;;;GASG;AACH,MAAM,WAAW,eAAe;IAC9B,oEAAoE;IACpE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IAErB;;;;;;;;;OASG;IACH,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,aAAa,EAAE,GAAG,IAAI,CAAA;IAE/D;;;;;;;;OAQG;IACH,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CAAA;IAE5D;;;;;;;;;OASG;IACH,eAAe,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,aAAa,CAAC,CAAA;IAExD;;;;;;;;OAQG;IACH,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;CACjD"}
|