From da87c670aeedd967f423a14ed1ff75d1d63625e7 Mon Sep 17 00:00:00 2001 From: insleker Date: Sat, 4 Apr 2026 18:52:01 +0800 Subject: [PATCH] feat: 1st commit --- .gitignore | 31 +- CHANGELOG.md | 3 + README.md | 102 +- analysis_options.yaml | 30 + bin/code_work_spawner.dart | 55 + docs/arch.md | 93 + lib/code_work_spawner.dart | 4 + lib/src/cli_responder.dart | 145 + lib/src/config.dart | 321 +++ lib/src/database.dart | 275 ++ lib/src/database.g.dart | 4519 ++++++++++++++++++++++++++++++ lib/src/github_client.dart | 227 ++ lib/src/issue_assistant_app.dart | 323 +++ lib/src/workspace_manager.dart | 62 + pubspec.yaml | 22 + test/code_work_spawner_test.dart | 304 ++ 16 files changed, 6488 insertions(+), 28 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 analysis_options.yaml create mode 100644 bin/code_work_spawner.dart create mode 100644 docs/arch.md create mode 100644 lib/code_work_spawner.dart create mode 100644 lib/src/cli_responder.dart create mode 100644 lib/src/config.dart create mode 100644 lib/src/database.dart create mode 100644 lib/src/database.g.dart create mode 100644 lib/src/github_client.dart create mode 100644 lib/src/issue_assistant_app.dart create mode 100644 lib/src/workspace_manager.dart create mode 100644 pubspec.yaml create mode 100644 test/code_work_spawner_test.dart diff --git a/.gitignore b/.gitignore index 3150b40..60b1dc4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,29 +1,6 @@ -# See https://www.dartlang.org/guides/libraries/private-files - -# Files and directories created by pub +# https://dart.dev/guides/libraries/private-files +# Created by `dart pub` .dart_tool/ -.packages -build/ -# If you're building an application, you may want to check-in your pubspec.lock pubspec.lock - -# Directory created by dartdoc -# If you don't generate documentation locally you can remove this line. -doc/api/ - -# dotenv environment variables file -.env* - -# Avoid committing generated Javascript files: -*.dart.js -# Produced by the --dump-info flag. -*.info.json -# When generated by dart2js. Don't specify *.js if your -# project includes source files written in JavaScript. -*.js -*.js_ -*.js.deps -*.js.map - -.flutter-plugins -.flutter-plugins-dependencies +.code_work_spawner.sqlite3 +agent-orchestrator.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..effe43c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +## 1.0.0 + +- Initial version. diff --git a/README.md b/README.md index f20daaf..ce5fdde 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,102 @@ # code_work_spawner -spawn multi agents to complete coding work + +GitHub issue thread assistant with Agent Orchestrator style config. + +The app polls configured GitHub repositories with `gh`, reads issue threads and +issue comments, and uses configured CLI responders such as `codex`, +`opencode`, or `copilot-cli` to reply when: + +- a user explicitly mentions the assistant +- a new comment changes the thread enough that the responder decides a reply is + useful + +It supports multi-repo configuration, responder fallback chains, Drift-backed +SQLite state, and BDD-style Dart tests. + +## Features + +- Agent Orchestrator style YAML config with `defaults` and `projects` +- Multiple repositories in one process +- Comment-triggered issue assistance +- Planning / architecture guidance in issue threads +- Bug verification flows in isolated temporary git worktrees +- CLI-based responder fallback chain +- Durable state with `drift` + SQLite +- BDD-style tests using `package:test` with Given / When / Then wording + +## Requirements + +- Dart SDK 3.11+ +- `gh` CLI authenticated for the target repositories +- Local checkouts for configured repositories +- Optional local responder CLIs such as `codex`, `opencode`, or `copilot-cli` + +## Config + +Create a local `agent-orchestrator.yaml`: + +```yaml +defaults: + issueAssistant: + enabled: true + pollInterval: 5m + mentionTriggers: ["@helper", "@codex"] + commentTag: code-work-spawner + prompt: | + You are helping on {{repo}}. + Trigger: {{trigger}} + Thread: + {{thread_json}} + responders: + - id: codex + command: codex + args: ["exec", "--json-output"] + timeout: 10m + - id: opencode + command: opencode + timeout: 10m + +projects: + my-app: + repo: owner/my-app + path: ~/code/my-app + defaultBranch: main + sessionPrefix: app +``` + +Project entries can override `issueAssistant` fields when a repo needs a +different prompt, mention triggers, or responder chain. + +## Run + +Single poll cycle: + +```bash +dart run bin/code_work_spawner.dart --once +``` + +Long-running poller: + +```bash +dart run bin/code_work_spawner.dart +``` + +Override paths when needed: + +```bash +dart run bin/code_work_spawner.dart \ + --config /path/to/agent-orchestrator.yaml \ + --db /path/to/state.sqlite3 \ + --gh-command /usr/bin/gh +``` + +## Testing + +```bash +dart test +dart analyze +``` + +## ref + +[agent-orchestrator](https://github.com/ComposioHQ/agent-orchestrator) diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..dee8927 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,30 @@ +# This file configures the static analysis results for your project (errors, +# warnings, and lints). +# +# This enables the 'recommended' set of lints from `package:lints`. +# This set helps identify many issues that may lead to problems when running +# or consuming Dart code, and enforces writing Dart using a single, idiomatic +# style and format. +# +# If you want a smaller set of lints you can change this to specify +# 'package:lints/core.yaml'. These are just the most critical lints +# (the recommended set includes the core lints). +# The core lints are also what is used by pub.dev for scoring packages. + +include: package:lints/recommended.yaml + +# Uncomment the following section to specify additional rules. + +# linter: +# rules: +# - camel_case_types + +# analyzer: +# exclude: +# - path/to/excluded/files/** + +# For more information about the core and recommended set of lints, see +# https://dart.dev/go/core-lints + +# For additional information about configuring this file, see +# https://dart.dev/guides/language/analysis-options diff --git a/bin/code_work_spawner.dart b/bin/code_work_spawner.dart new file mode 100644 index 0000000..701319b --- /dev/null +++ b/bin/code_work_spawner.dart @@ -0,0 +1,55 @@ +import 'dart:io'; + +import 'package:args/args.dart'; +import 'package:code_work_spawner/code_work_spawner.dart'; + +Future main(List arguments) async { + final parser = ArgParser() + ..addFlag( + 'once', + abbr: '1', + help: 'Run a single poll cycle and exit.', + negatable: false, + ) + ..addOption( + 'config', + abbr: 'c', + help: 'Path to agent-orchestrator.yaml.', + defaultsTo: 'agent-orchestrator.yaml', + ) + ..addOption( + 'db', + help: 'Path to the SQLite database file.', + defaultsTo: '.code_work_spawner.sqlite3', + ) + ..addOption( + 'gh-command', + help: 'Path to the gh executable.', + defaultsTo: 'gh', + ) + ..addFlag('help', abbr: 'h', negatable: false); + + final results = parser.parse(arguments); + if (results['help'] as bool) { + stdout.writeln(parser.usage); + return; + } + + final config = await AppConfig.load(results['config'] as String); + final app = await IssueAssistantApp.open( + config: config, + databasePath: results['db'] as String, + ghCommand: results['gh-command'] as String, + ); + + try { + if (results['once'] as bool) { + await app.runOnce(); + return; + } + + await app.run(); + } finally { + await app.close(); + } +} diff --git a/docs/arch.md b/docs/arch.md new file mode 100644 index 0000000..6a1b0be --- /dev/null +++ b/docs/arch.md @@ -0,0 +1,93 @@ +# arch + + +## coding flow + +```mermaid +flowchart TD + n4["agents implement"] + n5["agents create worktree, branch"] + n5 + n5 --> n4 + n6["create PR"] + n8["human merge"] + n9@{ shape: "diam", label: "human review" } + n5["agents create worktree, branch"] + n10@{ shape: "diam", label: "infra CI" } + n4 + n10 + n10 -->|"y"| n9 + n9 -->|"y"| n8 + n4 --> n6 + n6["upsert PR"] --> n10 + n10 -->|"n"| n4 + n9 -->|"n"| n4 + style n1 fill:#5CE1E6 + style n7 fill:#C1FF72 + style n9 fill:#C1FF72 + style n8 fill:#C1FF72 + style n3 fill:#5CE1E6 + subgraph s1["planing stage"] + n3["issue session in chat"] + n11["agents propose plan"] + n2["agents propose plan"] + n7@{ shape: "diam", label: "user review plan" } + n1["create issue"] + end + n1 + n1 + n7 + n7 + n7 + n7 -->|"y"| n5 + n7 + n2 --> n7 + n7 -->|"n: human prompt"| n2 + n1 --> n2 + n2 -->|"split to issues"| n1 + n11 --> n7 + n7 -->|"n: human prompt"| n11 + n11 + n11 + n3 --> n11 + n11 -->|"split to issue sessions"| n3 +``` + + +## arch + +```mermaid +flowchart + s2 + s2 + s2 + s2 + s2 + n3["ssh"] + subgraph s3["my pc"] + subgraph s4["spawner"] + n7["db for cache"] + n6["config file"] + n2["codex2"] + n5["codex1"] + end + end + s2 + s2 + s2 + s2 + n3 --> s3 + s2 -->|"poll through API to execute job"| s4 + s4 -->|"report execute status through API"| s2 + n1@{ label: "Rectangle" } + n1["runners dashboard"] + s3 + n1 --> s4 + subgraph s5["github"] + subgraph s2["middle record server"] + n4["db
save tmux session id, or codex session id, success/fail..."] + end + s1["workflow dashboard"] + end + s1 --> s2 +``` \ No newline at end of file diff --git a/lib/code_work_spawner.dart b/lib/code_work_spawner.dart new file mode 100644 index 0000000..1383527 --- /dev/null +++ b/lib/code_work_spawner.dart @@ -0,0 +1,4 @@ +export 'src/config.dart'; +export 'src/database.dart'; +export 'src/github_client.dart'; +export 'src/issue_assistant_app.dart'; diff --git a/lib/src/cli_responder.dart b/lib/src/cli_responder.dart new file mode 100644 index 0000000..6e59b39 --- /dev/null +++ b/lib/src/cli_responder.dart @@ -0,0 +1,145 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'config.dart'; +import 'github_client.dart'; + +class CliResponderRunner { + Future run({ + required CliResponderConfig responder, + required ProjectConfig project, + required IssueThread thread, + required String prompt, + required bool forceReply, + required String workspacePath, + }) async { + final context = { + 'prompt': prompt, + 'repo': project.repo, + 'project_key': project.key, + 'project_path': project.path, + 'workspace_path': workspacePath, + 'issue_number': thread.issue.number.toString(), + 'thread_json': encodePrettyJson(thread.toJson()), + 'force_reply': forceReply.toString(), + }; + + final stdinPayload = renderTemplate(responder.stdinTemplate, context); + final process = await Process.start( + responder.command, + responder.args, + workingDirectory: workspacePath, + environment: { + ...Platform.environment, + ...responder.env, + 'CWS_PROJECT_KEY': project.key, + 'CWS_REPO': project.repo, + 'CWS_PROJECT_PATH': project.path, + 'CWS_WORKSPACE_PATH': workspacePath, + 'CWS_ISSUE_NUMBER': thread.issue.number.toString(), + 'CWS_FORCE_REPLY': forceReply.toString(), + }, + ); + + process.stdin.write(stdinPayload); + await process.stdin.close(); + + final stdoutFuture = process.stdout.transform(utf8.decoder).join(); + final stderrFuture = process.stderr.transform(utf8.decoder).join(); + + late final int exitCode; + try { + exitCode = await process.exitCode.timeout(responder.timeout); + } on TimeoutException { + process.kill(ProcessSignal.sigkill); + throw TimeoutException( + 'Responder "${responder.id}" timed out after ${responder.timeout}.', + ); + } + + final stdoutText = await stdoutFuture; + final stderrText = await stderrFuture; + if (exitCode != 0) { + throw ProcessException( + responder.command, + responder.args, + stderrText.isEmpty ? stdoutText : stderrText, + exitCode, + ); + } + + final decoded = _decodeJson(stdoutText); + return ResponderResult.fromJson( + decoded, + responderId: responder.id, + rawStdout: stdoutText, + rawStderr: stderrText, + ); + } + + Map _decodeJson(String stdout) { + final trimmed = stdout.trim(); + if (trimmed.isEmpty) { + throw const FormatException('Responder returned empty stdout.'); + } + + try { + return (jsonDecode(trimmed) as Map).cast(); + } on FormatException { + final first = trimmed.indexOf('{'); + final last = trimmed.lastIndexOf('}'); + if (first == -1 || last <= first) { + rethrow; + } + final snippet = trimmed.substring(first, last + 1); + return (jsonDecode(snippet) as Map).cast(); + } + } +} + +class ResponderResult { + ResponderResult({ + required this.responderId, + required this.decision, + required this.mode, + required this.markdown, + required this.summary, + required this.rawStdout, + required this.rawStderr, + }); + + final String responderId; + final String decision; + final String mode; + final String markdown; + final String summary; + final String rawStdout; + final String rawStderr; + + bool get shouldReply => decision == 'reply'; + + factory ResponderResult.fromJson( + Map json, { + required String responderId, + required String rawStdout, + required String rawStderr, + }) { + final decision = json['decision']?.toString() ?? 'no_reply'; + final mode = json['mode']?.toString() ?? 'planning'; + final markdown = json['markdown']?.toString() ?? ''; + final summary = json['summary']?.toString() ?? ''; + if (decision != 'reply' && decision != 'no_reply') { + throw FormatException('Invalid decision "$decision".'); + } + return ResponderResult( + responderId: responderId, + decision: decision, + mode: mode, + markdown: markdown, + summary: summary, + rawStdout: rawStdout, + rawStderr: rawStderr, + ); + } +} diff --git a/lib/src/config.dart b/lib/src/config.dart new file mode 100644 index 0000000..d0d2f9a --- /dev/null +++ b/lib/src/config.dart @@ -0,0 +1,321 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:path/path.dart' as p; +import 'package:yaml/yaml.dart'; + +class AppConfig { + AppConfig({ + required this.configPath, + required this.defaults, + required this.projects, + }); + + final String configPath; + final IssueAssistantConfig defaults; + final Map projects; + + static Future load(String path) async { + final file = File(path); + final contents = await file.readAsString(); + final yaml = loadYaml(contents); + if (yaml is! YamlMap) { + throw const FormatException('Config root must be a YAML map.'); + } + + final defaultsMap = _asMap(yaml['defaults']); + final defaultAssistantMap = _asMap(defaultsMap['issueAssistant']); + final defaults = IssueAssistantConfig.fromMap(defaultAssistantMap); + + final projectsNode = yaml['projects']; + if (projectsNode is! YamlMap || projectsNode.isEmpty) { + throw const FormatException('projects must be a non-empty map.'); + } + + final projects = {}; + for (final entry in projectsNode.entries) { + final key = entry.key.toString(); + final value = _asMap(entry.value); + projects[key] = ProjectConfig.fromMap( + key: key, + map: value, + defaultAssistant: defaults, + defaultAssistantMap: defaultAssistantMap, + ); + } + + return AppConfig( + configPath: p.normalize(p.absolute(path)), + defaults: defaults, + projects: projects, + ); + } + + static Map _asMap(Object? value) { + if (value == null) { + return {}; + } + + if (value is YamlMap) { + return value.map( + (key, dynamicValue) => MapEntry(key.toString(), dynamicValue), + ); + } + + if (value is Map) { + return value; + } + + throw FormatException('Expected map, got ${value.runtimeType}.'); + } +} + +class ProjectConfig { + ProjectConfig({ + required this.key, + required this.repo, + required this.path, + required this.defaultBranch, + required this.sessionPrefix, + required this.issueAssistant, + }); + + final String key; + final String repo; + final String path; + final String defaultBranch; + final String sessionPrefix; + final IssueAssistantConfig issueAssistant; + + factory ProjectConfig.fromMap({ + required String key, + required Map map, + required IssueAssistantConfig defaultAssistant, + required Map defaultAssistantMap, + }) { + final assistantMap = AppConfig._asMap(map['issueAssistant']); + final mergedAssistantMap = { + ...defaultAssistantMap, + ...assistantMap, + }; + return ProjectConfig( + key: key, + repo: map['repo']?.toString() ?? _missing('projects.$key.repo'), + path: _expandHome( + map['path']?.toString() ?? _missing('projects.$key.path'), + ), + defaultBranch: + map['defaultBranch']?.toString() ?? + map['default_branch']?.toString() ?? + 'main', + sessionPrefix: + map['sessionPrefix']?.toString() ?? + map['session_prefix']?.toString() ?? + _deriveSessionPrefix(key), + issueAssistant: assistantMap.isEmpty + ? defaultAssistant + : IssueAssistantConfig.fromMap(mergedAssistantMap), + ); + } + + static String _missing(String field) { + throw FormatException('Missing required config field: $field'); + } +} + +class IssueAssistantConfig { + IssueAssistantConfig({ + required this.enabled, + required this.pollInterval, + required this.mentionTriggers, + required this.prompt, + required this.responders, + required this.capabilities, + required this.commentTag, + }); + + final bool enabled; + final Duration pollInterval; + final List mentionTriggers; + final String prompt; + final List responders; + final Set capabilities; + final String commentTag; + + factory IssueAssistantConfig.fromMap(Map map) { + final respondersNode = map['responders']; + final responders = respondersNode is List + ? respondersNode + .map((item) => CliResponderConfig.fromMap(AppConfig._asMap(item))) + .toList(growable: false) + : const []; + + final capabilitiesNode = map['capabilities']; + final capabilities = capabilitiesNode is List + ? capabilitiesNode + .map((item) => AssistantCapability.parse(item.toString())) + .toSet() + : {AssistantCapability.planning, AssistantCapability.bugVerification}; + + return IssueAssistantConfig( + enabled: map['enabled'] as bool? ?? true, + pollInterval: _parseDuration(map['pollInterval']?.toString() ?? '5m'), + mentionTriggers: + (map['mentionTriggers'] as List?) + ?.map((item) => item.toString()) + .toList(growable: false) ?? + const [], + prompt: map['prompt']?.toString() ?? _defaultPrompt, + responders: responders, + capabilities: capabilities, + commentTag: map['commentTag']?.toString() ?? 'code-work-spawner', + ); + } + static Duration _parseDuration(String input) { + final match = RegExp(r'^(\d+)([smhd])$').firstMatch(input.trim()); + if (match == null) { + throw FormatException('Invalid duration: $input'); + } + + final value = int.parse(match.group(1)!); + return switch (match.group(2)!) { + 's' => Duration(seconds: value), + 'm' => Duration(minutes: value), + 'h' => Duration(hours: value), + 'd' => Duration(days: value), + _ => throw FormatException('Invalid duration unit: $input'), + }; + } + + static const String _defaultPrompt = ''' +You are a GitHub issue thread assistant. + +You will receive repository metadata, the current issue thread as JSON, and the latest trigger context. + +Decide whether to reply. Rules: +- If the latest trigger explicitly mentions the assistant, always return "reply". +- If the discussion already has a good enough answer, return "no_reply". +- Supported modes are "planning" and "bug_verification". +- For planning requests, provide practical architecture and implementation guidance. +- For bug_verification requests, you may inspect the local repository and run temporary tests in the current workspace. Do not create commits or persistent artifacts. +- Always return strict JSON only. + +JSON schema: +{ + "decision": "reply" | "no_reply", + "mode": "planning" | "bug_verification", + "markdown": "markdown response, omitted for no_reply", + "summary": "short internal summary" +} +'''; +} + +class CliResponderConfig { + CliResponderConfig({ + required this.id, + required this.command, + required this.args, + required this.env, + required this.stdinTemplate, + required this.timeout, + }); + + final String id; + final String command; + final List args; + final Map env; + final String stdinTemplate; + final Duration timeout; + + factory CliResponderConfig.fromMap(Map map) { + final id = map['id']?.toString(); + final command = map['command']?.toString(); + if (id == null || id.isEmpty) { + throw const FormatException('Responder id is required.'); + } + if (command == null || command.isEmpty) { + throw FormatException('Responder "$id" must define command.'); + } + + final args = + (map['args'] as List?) + ?.map((item) => item.toString()) + .toList(growable: false) ?? + const []; + final env = AppConfig._asMap( + map['env'], + ).map((key, value) => MapEntry(key, value.toString())); + + return CliResponderConfig( + id: id, + command: command, + args: args, + env: env, + stdinTemplate: map['stdinTemplate']?.toString() ?? '{{prompt}}', + timeout: IssueAssistantConfig._parseDuration( + map['timeout']?.toString() ?? '10m', + ), + ); + } +} + +enum AssistantCapability { + planning, + bugVerification; + + static AssistantCapability parse(String input) { + return switch (input) { + 'planning' => AssistantCapability.planning, + 'bugVerification' || + 'bug_verification' => AssistantCapability.bugVerification, + _ => throw FormatException('Unsupported capability: $input'), + }; + } +} + +String renderTemplate(String template, Map values) { + var output = template; + for (final entry in values.entries) { + output = output.replaceAll('{{${entry.key}}}', entry.value); + } + return output; +} + +String encodePrettyJson(Object value) { + const encoder = JsonEncoder.withIndent(' '); + return encoder.convert(value); +} + +String _expandHome(String path) { + if (!path.startsWith('~/')) { + return p.normalize(p.absolute(path)); + } + + final home = Platform.environment['HOME']; + if (home == null || home.isEmpty) { + throw const FileSystemException('HOME is not set.'); + } + return p.normalize(p.join(home, path.substring(2))); +} + +String _deriveSessionPrefix(String key) { + if (key.contains('-')) { + return key + .split('-') + .where((part) => part.isNotEmpty) + .map((part) => part[0]) + .join() + .toLowerCase(); + } + + if (key.contains('_')) { + return key + .split('_') + .where((part) => part.isNotEmpty) + .map((part) => part[0]) + .join() + .toLowerCase(); + } + + return key.substring(0, key.length < 3 ? key.length : 3).toLowerCase(); +} diff --git a/lib/src/database.dart b/lib/src/database.dart new file mode 100644 index 0000000..0a5c85a --- /dev/null +++ b/lib/src/database.dart @@ -0,0 +1,275 @@ +import 'dart:io'; + +import 'package:drift/drift.dart'; +import 'package:drift/native.dart'; +import 'package:path/path.dart' as p; + +part 'database.g.dart'; + +class ProjectStates extends Table { + TextColumn get projectKey => text()(); + TextColumn get repo => text()(); + TextColumn get lastSeenUpdatedAt => text().nullable()(); + TextColumn get updatedAt => text()(); + + @override + Set> get primaryKey => {projectKey}; +} + +class IssueThreadStates extends Table { + IntColumn get id => integer().autoIncrement()(); + TextColumn get projectKey => text()(); + IntColumn get issueNumber => integer()(); + TextColumn get fingerprint => text()(); + TextColumn get lastEvaluatedFingerprint => text().nullable()(); + TextColumn get lastDecision => text().nullable()(); + IntColumn get lastCommentId => integer().nullable()(); + TextColumn get lastCommentUrl => text().nullable()(); + TextColumn get updatedAt => text()(); + + @override + List>> get uniqueKeys => [ + {projectKey, issueNumber}, + ]; +} + +class Jobs extends Table { + IntColumn get id => integer().autoIncrement()(); + TextColumn get projectKey => text()(); + IntColumn get issueNumber => integer()(); + TextColumn get fingerprint => text()(); + TextColumn get trigger => text()(); + TextColumn get status => text()(); + TextColumn get createdAt => text()(); + TextColumn get updatedAt => text()(); +} + +class ExecutionRuns extends Table { + IntColumn get id => integer().autoIncrement()(); + IntColumn get jobId => integer()(); + TextColumn get responderId => text()(); + TextColumn get mode => text()(); + TextColumn get decision => text()(); + BoolColumn get success => boolean()(); + TextColumn get workspacePath => text().nullable()(); + TextColumn get stdout => text()(); + TextColumn get stderr => text()(); + TextColumn get createdAt => text()(); +} + +class Comments extends Table { + IntColumn get id => integer().autoIncrement()(); + TextColumn get projectKey => text()(); + IntColumn get issueNumber => integer()(); + TextColumn get fingerprint => text()(); + IntColumn get githubCommentId => integer()(); + TextColumn get githubCommentUrl => text()(); + TextColumn get body => text()(); + TextColumn get createdAt => text()(); +} + +class PollRuns extends Table { + IntColumn get id => integer().autoIncrement()(); + TextColumn get projectKey => text()(); + TextColumn get startedAt => text()(); + TextColumn get finishedAt => text().nullable()(); + BoolColumn get success => boolean().nullable()(); + TextColumn get error => text().nullable()(); +} + +@DriftDatabase( + tables: [ + ProjectStates, + IssueThreadStates, + Jobs, + ExecutionRuns, + Comments, + PollRuns, + ], +) +class AppDatabase extends _$AppDatabase { + AppDatabase._(super.e); + + factory AppDatabase.open(String path) { + return AppDatabase._( + NativeDatabase.createInBackground(File(p.absolute(path))), + ); + } + + @override + int get schemaVersion => 1; + + Future findProjectState(String projectKey) { + return (select( + projectStates, + )..where((tbl) => tbl.projectKey.equals(projectKey))).getSingleOrNull(); + } + + Future upsertProjectState({ + required String projectKey, + required String repo, + required DateTime? lastSeenUpdatedAt, + }) { + final now = DateTime.now().toUtc().toIso8601String(); + return into(projectStates).insertOnConflictUpdate( + ProjectStatesCompanion.insert( + projectKey: projectKey, + repo: repo, + lastSeenUpdatedAt: Value(lastSeenUpdatedAt?.toIso8601String()), + updatedAt: now, + ), + ); + } + + Future findIssueThread( + String projectKey, + int issueNumber, + ) { + return (select(issueThreadStates) + ..where((tbl) => tbl.projectKey.equals(projectKey)) + ..where((tbl) => tbl.issueNumber.equals(issueNumber))) + .getSingleOrNull(); + } + + Future upsertIssueThread({ + required String projectKey, + required int issueNumber, + required String fingerprint, + required DateTime updatedAt, + String? lastEvaluatedFingerprint, + String? lastDecision, + int? lastCommentId, + String? lastCommentUrl, + }) async { + final existing = await findIssueThread(projectKey, issueNumber); + final companion = IssueThreadStatesCompanion( + projectKey: Value(projectKey), + issueNumber: Value(issueNumber), + fingerprint: Value(fingerprint), + lastEvaluatedFingerprint: Value(lastEvaluatedFingerprint), + lastDecision: Value(lastDecision), + lastCommentId: Value(lastCommentId), + lastCommentUrl: Value(lastCommentUrl), + updatedAt: Value(updatedAt.toUtc().toIso8601String()), + ); + + if (existing == null) { + await into(issueThreadStates).insert( + IssueThreadStatesCompanion.insert( + projectKey: projectKey, + issueNumber: issueNumber, + fingerprint: fingerprint, + updatedAt: updatedAt.toUtc().toIso8601String(), + lastEvaluatedFingerprint: Value(lastEvaluatedFingerprint), + lastDecision: Value(lastDecision), + lastCommentId: Value(lastCommentId), + lastCommentUrl: Value(lastCommentUrl), + ), + ); + return; + } + + await (update( + issueThreadStates, + )..where((tbl) => tbl.id.equals(existing.id))).write(companion); + } + + Future createJob({ + required String projectKey, + required int issueNumber, + required String fingerprint, + required String trigger, + required String status, + }) { + final now = DateTime.now().toUtc().toIso8601String(); + return into(jobs).insert( + JobsCompanion.insert( + projectKey: projectKey, + issueNumber: issueNumber, + fingerprint: fingerprint, + trigger: trigger, + status: status, + createdAt: now, + updatedAt: now, + ), + ); + } + + Future updateJobStatus(int jobId, String status) { + return (update(jobs)..where((tbl) => tbl.id.equals(jobId))).write( + JobsCompanion( + status: Value(status), + updatedAt: Value(DateTime.now().toUtc().toIso8601String()), + ), + ); + } + + Future addExecutionRun({ + required int jobId, + required String responderId, + required String mode, + required String decision, + required bool success, + required String workspacePath, + required String stdout, + required String stderr, + }) { + return into(executionRuns).insert( + ExecutionRunsCompanion.insert( + jobId: jobId, + responderId: responderId, + mode: mode, + decision: decision, + success: success, + workspacePath: Value(workspacePath), + stdout: stdout, + stderr: stderr, + createdAt: DateTime.now().toUtc().toIso8601String(), + ), + ); + } + + Future addCommentRecord({ + required String projectKey, + required int issueNumber, + required String fingerprint, + required int githubCommentId, + required String githubCommentUrl, + required String body, + }) { + return into(comments).insert( + CommentsCompanion.insert( + projectKey: projectKey, + issueNumber: issueNumber, + fingerprint: fingerprint, + githubCommentId: githubCommentId, + githubCommentUrl: githubCommentUrl, + body: body, + createdAt: DateTime.now().toUtc().toIso8601String(), + ), + ); + } + + Future startPollRun(String projectKey) { + return into(pollRuns).insert( + PollRunsCompanion.insert( + projectKey: projectKey, + startedAt: DateTime.now().toUtc().toIso8601String(), + ), + ); + } + + Future finishPollRun({ + required int pollRunId, + required bool success, + String? error, + }) { + return (update(pollRuns)..where((tbl) => tbl.id.equals(pollRunId))).write( + PollRunsCompanion( + finishedAt: Value(DateTime.now().toUtc().toIso8601String()), + success: Value(success), + error: Value(error), + ), + ); + } +} diff --git a/lib/src/database.g.dart b/lib/src/database.g.dart new file mode 100644 index 0000000..eddc054 --- /dev/null +++ b/lib/src/database.g.dart @@ -0,0 +1,4519 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'database.dart'; + +// ignore_for_file: type=lint +class $ProjectStatesTable extends ProjectStates + with TableInfo<$ProjectStatesTable, ProjectState> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $ProjectStatesTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _projectKeyMeta = const VerificationMeta( + 'projectKey', + ); + @override + late final GeneratedColumn projectKey = GeneratedColumn( + 'project_key', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _repoMeta = const VerificationMeta('repo'); + @override + late final GeneratedColumn repo = GeneratedColumn( + 'repo', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _lastSeenUpdatedAtMeta = const VerificationMeta( + 'lastSeenUpdatedAt', + ); + @override + late final GeneratedColumn lastSeenUpdatedAt = + GeneratedColumn( + 'last_seen_updated_at', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _updatedAtMeta = const VerificationMeta( + 'updatedAt', + ); + @override + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + projectKey, + repo, + lastSeenUpdatedAt, + updatedAt, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'project_states'; + @override + VerificationContext validateIntegrity( + Insertable instance, { + bool isInserting = false, + }) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('project_key')) { + context.handle( + _projectKeyMeta, + projectKey.isAcceptableOrUnknown(data['project_key']!, _projectKeyMeta), + ); + } else if (isInserting) { + context.missing(_projectKeyMeta); + } + if (data.containsKey('repo')) { + context.handle( + _repoMeta, + repo.isAcceptableOrUnknown(data['repo']!, _repoMeta), + ); + } else if (isInserting) { + context.missing(_repoMeta); + } + if (data.containsKey('last_seen_updated_at')) { + context.handle( + _lastSeenUpdatedAtMeta, + lastSeenUpdatedAt.isAcceptableOrUnknown( + data['last_seen_updated_at']!, + _lastSeenUpdatedAtMeta, + ), + ); + } + if (data.containsKey('updated_at')) { + context.handle( + _updatedAtMeta, + updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta), + ); + } else if (isInserting) { + context.missing(_updatedAtMeta); + } + return context; + } + + @override + Set get $primaryKey => {projectKey}; + @override + ProjectState map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return ProjectState( + projectKey: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}project_key'], + )!, + repo: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}repo'], + )!, + lastSeenUpdatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}last_seen_updated_at'], + ), + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}updated_at'], + )!, + ); + } + + @override + $ProjectStatesTable createAlias(String alias) { + return $ProjectStatesTable(attachedDatabase, alias); + } +} + +class ProjectState extends DataClass implements Insertable { + final String projectKey; + final String repo; + final String? lastSeenUpdatedAt; + final String updatedAt; + const ProjectState({ + required this.projectKey, + required this.repo, + this.lastSeenUpdatedAt, + required this.updatedAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['project_key'] = Variable(projectKey); + map['repo'] = Variable(repo); + if (!nullToAbsent || lastSeenUpdatedAt != null) { + map['last_seen_updated_at'] = Variable(lastSeenUpdatedAt); + } + map['updated_at'] = Variable(updatedAt); + return map; + } + + ProjectStatesCompanion toCompanion(bool nullToAbsent) { + return ProjectStatesCompanion( + projectKey: Value(projectKey), + repo: Value(repo), + lastSeenUpdatedAt: lastSeenUpdatedAt == null && nullToAbsent + ? const Value.absent() + : Value(lastSeenUpdatedAt), + updatedAt: Value(updatedAt), + ); + } + + factory ProjectState.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return ProjectState( + projectKey: serializer.fromJson(json['projectKey']), + repo: serializer.fromJson(json['repo']), + lastSeenUpdatedAt: serializer.fromJson( + json['lastSeenUpdatedAt'], + ), + updatedAt: serializer.fromJson(json['updatedAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'projectKey': serializer.toJson(projectKey), + 'repo': serializer.toJson(repo), + 'lastSeenUpdatedAt': serializer.toJson(lastSeenUpdatedAt), + 'updatedAt': serializer.toJson(updatedAt), + }; + } + + ProjectState copyWith({ + String? projectKey, + String? repo, + Value lastSeenUpdatedAt = const Value.absent(), + String? updatedAt, + }) => ProjectState( + projectKey: projectKey ?? this.projectKey, + repo: repo ?? this.repo, + lastSeenUpdatedAt: lastSeenUpdatedAt.present + ? lastSeenUpdatedAt.value + : this.lastSeenUpdatedAt, + updatedAt: updatedAt ?? this.updatedAt, + ); + ProjectState copyWithCompanion(ProjectStatesCompanion data) { + return ProjectState( + projectKey: data.projectKey.present + ? data.projectKey.value + : this.projectKey, + repo: data.repo.present ? data.repo.value : this.repo, + lastSeenUpdatedAt: data.lastSeenUpdatedAt.present + ? data.lastSeenUpdatedAt.value + : this.lastSeenUpdatedAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ); + } + + @override + String toString() { + return (StringBuffer('ProjectState(') + ..write('projectKey: $projectKey, ') + ..write('repo: $repo, ') + ..write('lastSeenUpdatedAt: $lastSeenUpdatedAt, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(projectKey, repo, lastSeenUpdatedAt, updatedAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is ProjectState && + other.projectKey == this.projectKey && + other.repo == this.repo && + other.lastSeenUpdatedAt == this.lastSeenUpdatedAt && + other.updatedAt == this.updatedAt); +} + +class ProjectStatesCompanion extends UpdateCompanion { + final Value projectKey; + final Value repo; + final Value lastSeenUpdatedAt; + final Value updatedAt; + final Value rowid; + const ProjectStatesCompanion({ + this.projectKey = const Value.absent(), + this.repo = const Value.absent(), + this.lastSeenUpdatedAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.rowid = const Value.absent(), + }); + ProjectStatesCompanion.insert({ + required String projectKey, + required String repo, + this.lastSeenUpdatedAt = const Value.absent(), + required String updatedAt, + this.rowid = const Value.absent(), + }) : projectKey = Value(projectKey), + repo = Value(repo), + updatedAt = Value(updatedAt); + static Insertable custom({ + Expression? projectKey, + Expression? repo, + Expression? lastSeenUpdatedAt, + Expression? updatedAt, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (projectKey != null) 'project_key': projectKey, + if (repo != null) 'repo': repo, + if (lastSeenUpdatedAt != null) 'last_seen_updated_at': lastSeenUpdatedAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (rowid != null) 'rowid': rowid, + }); + } + + ProjectStatesCompanion copyWith({ + Value? projectKey, + Value? repo, + Value? lastSeenUpdatedAt, + Value? updatedAt, + Value? rowid, + }) { + return ProjectStatesCompanion( + projectKey: projectKey ?? this.projectKey, + repo: repo ?? this.repo, + lastSeenUpdatedAt: lastSeenUpdatedAt ?? this.lastSeenUpdatedAt, + updatedAt: updatedAt ?? this.updatedAt, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (projectKey.present) { + map['project_key'] = Variable(projectKey.value); + } + if (repo.present) { + map['repo'] = Variable(repo.value); + } + if (lastSeenUpdatedAt.present) { + map['last_seen_updated_at'] = Variable(lastSeenUpdatedAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('ProjectStatesCompanion(') + ..write('projectKey: $projectKey, ') + ..write('repo: $repo, ') + ..write('lastSeenUpdatedAt: $lastSeenUpdatedAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class $IssueThreadStatesTable extends IssueThreadStates + with TableInfo<$IssueThreadStatesTable, IssueThreadState> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $IssueThreadStatesTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'PRIMARY KEY AUTOINCREMENT', + ), + ); + static const VerificationMeta _projectKeyMeta = const VerificationMeta( + 'projectKey', + ); + @override + late final GeneratedColumn projectKey = GeneratedColumn( + 'project_key', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _issueNumberMeta = const VerificationMeta( + 'issueNumber', + ); + @override + late final GeneratedColumn issueNumber = GeneratedColumn( + 'issue_number', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + static const VerificationMeta _fingerprintMeta = const VerificationMeta( + 'fingerprint', + ); + @override + late final GeneratedColumn fingerprint = GeneratedColumn( + 'fingerprint', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _lastEvaluatedFingerprintMeta = + const VerificationMeta('lastEvaluatedFingerprint'); + @override + late final GeneratedColumn lastEvaluatedFingerprint = + GeneratedColumn( + 'last_evaluated_fingerprint', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _lastDecisionMeta = const VerificationMeta( + 'lastDecision', + ); + @override + late final GeneratedColumn lastDecision = GeneratedColumn( + 'last_decision', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _lastCommentIdMeta = const VerificationMeta( + 'lastCommentId', + ); + @override + late final GeneratedColumn lastCommentId = GeneratedColumn( + 'last_comment_id', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + static const VerificationMeta _lastCommentUrlMeta = const VerificationMeta( + 'lastCommentUrl', + ); + @override + late final GeneratedColumn lastCommentUrl = GeneratedColumn( + 'last_comment_url', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _updatedAtMeta = const VerificationMeta( + 'updatedAt', + ); + @override + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + projectKey, + issueNumber, + fingerprint, + lastEvaluatedFingerprint, + lastDecision, + lastCommentId, + lastCommentUrl, + updatedAt, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'issue_thread_states'; + @override + VerificationContext validateIntegrity( + Insertable instance, { + bool isInserting = false, + }) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('project_key')) { + context.handle( + _projectKeyMeta, + projectKey.isAcceptableOrUnknown(data['project_key']!, _projectKeyMeta), + ); + } else if (isInserting) { + context.missing(_projectKeyMeta); + } + if (data.containsKey('issue_number')) { + context.handle( + _issueNumberMeta, + issueNumber.isAcceptableOrUnknown( + data['issue_number']!, + _issueNumberMeta, + ), + ); + } else if (isInserting) { + context.missing(_issueNumberMeta); + } + if (data.containsKey('fingerprint')) { + context.handle( + _fingerprintMeta, + fingerprint.isAcceptableOrUnknown( + data['fingerprint']!, + _fingerprintMeta, + ), + ); + } else if (isInserting) { + context.missing(_fingerprintMeta); + } + if (data.containsKey('last_evaluated_fingerprint')) { + context.handle( + _lastEvaluatedFingerprintMeta, + lastEvaluatedFingerprint.isAcceptableOrUnknown( + data['last_evaluated_fingerprint']!, + _lastEvaluatedFingerprintMeta, + ), + ); + } + if (data.containsKey('last_decision')) { + context.handle( + _lastDecisionMeta, + lastDecision.isAcceptableOrUnknown( + data['last_decision']!, + _lastDecisionMeta, + ), + ); + } + if (data.containsKey('last_comment_id')) { + context.handle( + _lastCommentIdMeta, + lastCommentId.isAcceptableOrUnknown( + data['last_comment_id']!, + _lastCommentIdMeta, + ), + ); + } + if (data.containsKey('last_comment_url')) { + context.handle( + _lastCommentUrlMeta, + lastCommentUrl.isAcceptableOrUnknown( + data['last_comment_url']!, + _lastCommentUrlMeta, + ), + ); + } + if (data.containsKey('updated_at')) { + context.handle( + _updatedAtMeta, + updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta), + ); + } else if (isInserting) { + context.missing(_updatedAtMeta); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + List> get uniqueKeys => [ + {projectKey, issueNumber}, + ]; + @override + IssueThreadState map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return IssueThreadState( + id: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}id'], + )!, + projectKey: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}project_key'], + )!, + issueNumber: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}issue_number'], + )!, + fingerprint: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}fingerprint'], + )!, + lastEvaluatedFingerprint: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}last_evaluated_fingerprint'], + ), + lastDecision: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}last_decision'], + ), + lastCommentId: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}last_comment_id'], + ), + lastCommentUrl: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}last_comment_url'], + ), + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}updated_at'], + )!, + ); + } + + @override + $IssueThreadStatesTable createAlias(String alias) { + return $IssueThreadStatesTable(attachedDatabase, alias); + } +} + +class IssueThreadState extends DataClass + implements Insertable { + final int id; + final String projectKey; + final int issueNumber; + final String fingerprint; + final String? lastEvaluatedFingerprint; + final String? lastDecision; + final int? lastCommentId; + final String? lastCommentUrl; + final String updatedAt; + const IssueThreadState({ + required this.id, + required this.projectKey, + required this.issueNumber, + required this.fingerprint, + this.lastEvaluatedFingerprint, + this.lastDecision, + this.lastCommentId, + this.lastCommentUrl, + required this.updatedAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['project_key'] = Variable(projectKey); + map['issue_number'] = Variable(issueNumber); + map['fingerprint'] = Variable(fingerprint); + if (!nullToAbsent || lastEvaluatedFingerprint != null) { + map['last_evaluated_fingerprint'] = Variable( + lastEvaluatedFingerprint, + ); + } + if (!nullToAbsent || lastDecision != null) { + map['last_decision'] = Variable(lastDecision); + } + if (!nullToAbsent || lastCommentId != null) { + map['last_comment_id'] = Variable(lastCommentId); + } + if (!nullToAbsent || lastCommentUrl != null) { + map['last_comment_url'] = Variable(lastCommentUrl); + } + map['updated_at'] = Variable(updatedAt); + return map; + } + + IssueThreadStatesCompanion toCompanion(bool nullToAbsent) { + return IssueThreadStatesCompanion( + id: Value(id), + projectKey: Value(projectKey), + issueNumber: Value(issueNumber), + fingerprint: Value(fingerprint), + lastEvaluatedFingerprint: lastEvaluatedFingerprint == null && nullToAbsent + ? const Value.absent() + : Value(lastEvaluatedFingerprint), + lastDecision: lastDecision == null && nullToAbsent + ? const Value.absent() + : Value(lastDecision), + lastCommentId: lastCommentId == null && nullToAbsent + ? const Value.absent() + : Value(lastCommentId), + lastCommentUrl: lastCommentUrl == null && nullToAbsent + ? const Value.absent() + : Value(lastCommentUrl), + updatedAt: Value(updatedAt), + ); + } + + factory IssueThreadState.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return IssueThreadState( + id: serializer.fromJson(json['id']), + projectKey: serializer.fromJson(json['projectKey']), + issueNumber: serializer.fromJson(json['issueNumber']), + fingerprint: serializer.fromJson(json['fingerprint']), + lastEvaluatedFingerprint: serializer.fromJson( + json['lastEvaluatedFingerprint'], + ), + lastDecision: serializer.fromJson(json['lastDecision']), + lastCommentId: serializer.fromJson(json['lastCommentId']), + lastCommentUrl: serializer.fromJson(json['lastCommentUrl']), + updatedAt: serializer.fromJson(json['updatedAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'projectKey': serializer.toJson(projectKey), + 'issueNumber': serializer.toJson(issueNumber), + 'fingerprint': serializer.toJson(fingerprint), + 'lastEvaluatedFingerprint': serializer.toJson( + lastEvaluatedFingerprint, + ), + 'lastDecision': serializer.toJson(lastDecision), + 'lastCommentId': serializer.toJson(lastCommentId), + 'lastCommentUrl': serializer.toJson(lastCommentUrl), + 'updatedAt': serializer.toJson(updatedAt), + }; + } + + IssueThreadState copyWith({ + int? id, + String? projectKey, + int? issueNumber, + String? fingerprint, + Value lastEvaluatedFingerprint = const Value.absent(), + Value lastDecision = const Value.absent(), + Value lastCommentId = const Value.absent(), + Value lastCommentUrl = const Value.absent(), + String? updatedAt, + }) => IssueThreadState( + id: id ?? this.id, + projectKey: projectKey ?? this.projectKey, + issueNumber: issueNumber ?? this.issueNumber, + fingerprint: fingerprint ?? this.fingerprint, + lastEvaluatedFingerprint: lastEvaluatedFingerprint.present + ? lastEvaluatedFingerprint.value + : this.lastEvaluatedFingerprint, + lastDecision: lastDecision.present ? lastDecision.value : this.lastDecision, + lastCommentId: lastCommentId.present + ? lastCommentId.value + : this.lastCommentId, + lastCommentUrl: lastCommentUrl.present + ? lastCommentUrl.value + : this.lastCommentUrl, + updatedAt: updatedAt ?? this.updatedAt, + ); + IssueThreadState copyWithCompanion(IssueThreadStatesCompanion data) { + return IssueThreadState( + id: data.id.present ? data.id.value : this.id, + projectKey: data.projectKey.present + ? data.projectKey.value + : this.projectKey, + issueNumber: data.issueNumber.present + ? data.issueNumber.value + : this.issueNumber, + fingerprint: data.fingerprint.present + ? data.fingerprint.value + : this.fingerprint, + lastEvaluatedFingerprint: data.lastEvaluatedFingerprint.present + ? data.lastEvaluatedFingerprint.value + : this.lastEvaluatedFingerprint, + lastDecision: data.lastDecision.present + ? data.lastDecision.value + : this.lastDecision, + lastCommentId: data.lastCommentId.present + ? data.lastCommentId.value + : this.lastCommentId, + lastCommentUrl: data.lastCommentUrl.present + ? data.lastCommentUrl.value + : this.lastCommentUrl, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ); + } + + @override + String toString() { + return (StringBuffer('IssueThreadState(') + ..write('id: $id, ') + ..write('projectKey: $projectKey, ') + ..write('issueNumber: $issueNumber, ') + ..write('fingerprint: $fingerprint, ') + ..write('lastEvaluatedFingerprint: $lastEvaluatedFingerprint, ') + ..write('lastDecision: $lastDecision, ') + ..write('lastCommentId: $lastCommentId, ') + ..write('lastCommentUrl: $lastCommentUrl, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + projectKey, + issueNumber, + fingerprint, + lastEvaluatedFingerprint, + lastDecision, + lastCommentId, + lastCommentUrl, + updatedAt, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is IssueThreadState && + other.id == this.id && + other.projectKey == this.projectKey && + other.issueNumber == this.issueNumber && + other.fingerprint == this.fingerprint && + other.lastEvaluatedFingerprint == this.lastEvaluatedFingerprint && + other.lastDecision == this.lastDecision && + other.lastCommentId == this.lastCommentId && + other.lastCommentUrl == this.lastCommentUrl && + other.updatedAt == this.updatedAt); +} + +class IssueThreadStatesCompanion extends UpdateCompanion { + final Value id; + final Value projectKey; + final Value issueNumber; + final Value fingerprint; + final Value lastEvaluatedFingerprint; + final Value lastDecision; + final Value lastCommentId; + final Value lastCommentUrl; + final Value updatedAt; + const IssueThreadStatesCompanion({ + this.id = const Value.absent(), + this.projectKey = const Value.absent(), + this.issueNumber = const Value.absent(), + this.fingerprint = const Value.absent(), + this.lastEvaluatedFingerprint = const Value.absent(), + this.lastDecision = const Value.absent(), + this.lastCommentId = const Value.absent(), + this.lastCommentUrl = const Value.absent(), + this.updatedAt = const Value.absent(), + }); + IssueThreadStatesCompanion.insert({ + this.id = const Value.absent(), + required String projectKey, + required int issueNumber, + required String fingerprint, + this.lastEvaluatedFingerprint = const Value.absent(), + this.lastDecision = const Value.absent(), + this.lastCommentId = const Value.absent(), + this.lastCommentUrl = const Value.absent(), + required String updatedAt, + }) : projectKey = Value(projectKey), + issueNumber = Value(issueNumber), + fingerprint = Value(fingerprint), + updatedAt = Value(updatedAt); + static Insertable custom({ + Expression? id, + Expression? projectKey, + Expression? issueNumber, + Expression? fingerprint, + Expression? lastEvaluatedFingerprint, + Expression? lastDecision, + Expression? lastCommentId, + Expression? lastCommentUrl, + Expression? updatedAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (projectKey != null) 'project_key': projectKey, + if (issueNumber != null) 'issue_number': issueNumber, + if (fingerprint != null) 'fingerprint': fingerprint, + if (lastEvaluatedFingerprint != null) + 'last_evaluated_fingerprint': lastEvaluatedFingerprint, + if (lastDecision != null) 'last_decision': lastDecision, + if (lastCommentId != null) 'last_comment_id': lastCommentId, + if (lastCommentUrl != null) 'last_comment_url': lastCommentUrl, + if (updatedAt != null) 'updated_at': updatedAt, + }); + } + + IssueThreadStatesCompanion copyWith({ + Value? id, + Value? projectKey, + Value? issueNumber, + Value? fingerprint, + Value? lastEvaluatedFingerprint, + Value? lastDecision, + Value? lastCommentId, + Value? lastCommentUrl, + Value? updatedAt, + }) { + return IssueThreadStatesCompanion( + id: id ?? this.id, + projectKey: projectKey ?? this.projectKey, + issueNumber: issueNumber ?? this.issueNumber, + fingerprint: fingerprint ?? this.fingerprint, + lastEvaluatedFingerprint: + lastEvaluatedFingerprint ?? this.lastEvaluatedFingerprint, + lastDecision: lastDecision ?? this.lastDecision, + lastCommentId: lastCommentId ?? this.lastCommentId, + lastCommentUrl: lastCommentUrl ?? this.lastCommentUrl, + updatedAt: updatedAt ?? this.updatedAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (projectKey.present) { + map['project_key'] = Variable(projectKey.value); + } + if (issueNumber.present) { + map['issue_number'] = Variable(issueNumber.value); + } + if (fingerprint.present) { + map['fingerprint'] = Variable(fingerprint.value); + } + if (lastEvaluatedFingerprint.present) { + map['last_evaluated_fingerprint'] = Variable( + lastEvaluatedFingerprint.value, + ); + } + if (lastDecision.present) { + map['last_decision'] = Variable(lastDecision.value); + } + if (lastCommentId.present) { + map['last_comment_id'] = Variable(lastCommentId.value); + } + if (lastCommentUrl.present) { + map['last_comment_url'] = Variable(lastCommentUrl.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('IssueThreadStatesCompanion(') + ..write('id: $id, ') + ..write('projectKey: $projectKey, ') + ..write('issueNumber: $issueNumber, ') + ..write('fingerprint: $fingerprint, ') + ..write('lastEvaluatedFingerprint: $lastEvaluatedFingerprint, ') + ..write('lastDecision: $lastDecision, ') + ..write('lastCommentId: $lastCommentId, ') + ..write('lastCommentUrl: $lastCommentUrl, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } +} + +class $JobsTable extends Jobs with TableInfo<$JobsTable, Job> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $JobsTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'PRIMARY KEY AUTOINCREMENT', + ), + ); + static const VerificationMeta _projectKeyMeta = const VerificationMeta( + 'projectKey', + ); + @override + late final GeneratedColumn projectKey = GeneratedColumn( + 'project_key', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _issueNumberMeta = const VerificationMeta( + 'issueNumber', + ); + @override + late final GeneratedColumn issueNumber = GeneratedColumn( + 'issue_number', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + static const VerificationMeta _fingerprintMeta = const VerificationMeta( + 'fingerprint', + ); + @override + late final GeneratedColumn fingerprint = GeneratedColumn( + 'fingerprint', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _triggerMeta = const VerificationMeta( + 'trigger', + ); + @override + late final GeneratedColumn trigger = GeneratedColumn( + 'trigger', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _statusMeta = const VerificationMeta('status'); + @override + late final GeneratedColumn status = GeneratedColumn( + 'status', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _createdAtMeta = const VerificationMeta( + 'createdAt', + ); + @override + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _updatedAtMeta = const VerificationMeta( + 'updatedAt', + ); + @override + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + projectKey, + issueNumber, + fingerprint, + trigger, + status, + createdAt, + updatedAt, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'jobs'; + @override + VerificationContext validateIntegrity( + Insertable instance, { + bool isInserting = false, + }) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('project_key')) { + context.handle( + _projectKeyMeta, + projectKey.isAcceptableOrUnknown(data['project_key']!, _projectKeyMeta), + ); + } else if (isInserting) { + context.missing(_projectKeyMeta); + } + if (data.containsKey('issue_number')) { + context.handle( + _issueNumberMeta, + issueNumber.isAcceptableOrUnknown( + data['issue_number']!, + _issueNumberMeta, + ), + ); + } else if (isInserting) { + context.missing(_issueNumberMeta); + } + if (data.containsKey('fingerprint')) { + context.handle( + _fingerprintMeta, + fingerprint.isAcceptableOrUnknown( + data['fingerprint']!, + _fingerprintMeta, + ), + ); + } else if (isInserting) { + context.missing(_fingerprintMeta); + } + if (data.containsKey('trigger')) { + context.handle( + _triggerMeta, + trigger.isAcceptableOrUnknown(data['trigger']!, _triggerMeta), + ); + } else if (isInserting) { + context.missing(_triggerMeta); + } + if (data.containsKey('status')) { + context.handle( + _statusMeta, + status.isAcceptableOrUnknown(data['status']!, _statusMeta), + ); + } else if (isInserting) { + context.missing(_statusMeta); + } + if (data.containsKey('created_at')) { + context.handle( + _createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta), + ); + } else if (isInserting) { + context.missing(_createdAtMeta); + } + if (data.containsKey('updated_at')) { + context.handle( + _updatedAtMeta, + updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta), + ); + } else if (isInserting) { + context.missing(_updatedAtMeta); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + Job map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return Job( + id: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}id'], + )!, + projectKey: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}project_key'], + )!, + issueNumber: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}issue_number'], + )!, + fingerprint: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}fingerprint'], + )!, + trigger: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}trigger'], + )!, + status: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}status'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}updated_at'], + )!, + ); + } + + @override + $JobsTable createAlias(String alias) { + return $JobsTable(attachedDatabase, alias); + } +} + +class Job extends DataClass implements Insertable { + final int id; + final String projectKey; + final int issueNumber; + final String fingerprint; + final String trigger; + final String status; + final String createdAt; + final String updatedAt; + const Job({ + required this.id, + required this.projectKey, + required this.issueNumber, + required this.fingerprint, + required this.trigger, + required this.status, + required this.createdAt, + required this.updatedAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['project_key'] = Variable(projectKey); + map['issue_number'] = Variable(issueNumber); + map['fingerprint'] = Variable(fingerprint); + map['trigger'] = Variable(trigger); + map['status'] = Variable(status); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + return map; + } + + JobsCompanion toCompanion(bool nullToAbsent) { + return JobsCompanion( + id: Value(id), + projectKey: Value(projectKey), + issueNumber: Value(issueNumber), + fingerprint: Value(fingerprint), + trigger: Value(trigger), + status: Value(status), + createdAt: Value(createdAt), + updatedAt: Value(updatedAt), + ); + } + + factory Job.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return Job( + id: serializer.fromJson(json['id']), + projectKey: serializer.fromJson(json['projectKey']), + issueNumber: serializer.fromJson(json['issueNumber']), + fingerprint: serializer.fromJson(json['fingerprint']), + trigger: serializer.fromJson(json['trigger']), + status: serializer.fromJson(json['status']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'projectKey': serializer.toJson(projectKey), + 'issueNumber': serializer.toJson(issueNumber), + 'fingerprint': serializer.toJson(fingerprint), + 'trigger': serializer.toJson(trigger), + 'status': serializer.toJson(status), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + }; + } + + Job copyWith({ + int? id, + String? projectKey, + int? issueNumber, + String? fingerprint, + String? trigger, + String? status, + String? createdAt, + String? updatedAt, + }) => Job( + id: id ?? this.id, + projectKey: projectKey ?? this.projectKey, + issueNumber: issueNumber ?? this.issueNumber, + fingerprint: fingerprint ?? this.fingerprint, + trigger: trigger ?? this.trigger, + status: status ?? this.status, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ); + Job copyWithCompanion(JobsCompanion data) { + return Job( + id: data.id.present ? data.id.value : this.id, + projectKey: data.projectKey.present + ? data.projectKey.value + : this.projectKey, + issueNumber: data.issueNumber.present + ? data.issueNumber.value + : this.issueNumber, + fingerprint: data.fingerprint.present + ? data.fingerprint.value + : this.fingerprint, + trigger: data.trigger.present ? data.trigger.value : this.trigger, + status: data.status.present ? data.status.value : this.status, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ); + } + + @override + String toString() { + return (StringBuffer('Job(') + ..write('id: $id, ') + ..write('projectKey: $projectKey, ') + ..write('issueNumber: $issueNumber, ') + ..write('fingerprint: $fingerprint, ') + ..write('trigger: $trigger, ') + ..write('status: $status, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + projectKey, + issueNumber, + fingerprint, + trigger, + status, + createdAt, + updatedAt, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is Job && + other.id == this.id && + other.projectKey == this.projectKey && + other.issueNumber == this.issueNumber && + other.fingerprint == this.fingerprint && + other.trigger == this.trigger && + other.status == this.status && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt); +} + +class JobsCompanion extends UpdateCompanion { + final Value id; + final Value projectKey; + final Value issueNumber; + final Value fingerprint; + final Value trigger; + final Value status; + final Value createdAt; + final Value updatedAt; + const JobsCompanion({ + this.id = const Value.absent(), + this.projectKey = const Value.absent(), + this.issueNumber = const Value.absent(), + this.fingerprint = const Value.absent(), + this.trigger = const Value.absent(), + this.status = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + }); + JobsCompanion.insert({ + this.id = const Value.absent(), + required String projectKey, + required int issueNumber, + required String fingerprint, + required String trigger, + required String status, + required String createdAt, + required String updatedAt, + }) : projectKey = Value(projectKey), + issueNumber = Value(issueNumber), + fingerprint = Value(fingerprint), + trigger = Value(trigger), + status = Value(status), + createdAt = Value(createdAt), + updatedAt = Value(updatedAt); + static Insertable custom({ + Expression? id, + Expression? projectKey, + Expression? issueNumber, + Expression? fingerprint, + Expression? trigger, + Expression? status, + Expression? createdAt, + Expression? updatedAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (projectKey != null) 'project_key': projectKey, + if (issueNumber != null) 'issue_number': issueNumber, + if (fingerprint != null) 'fingerprint': fingerprint, + if (trigger != null) 'trigger': trigger, + if (status != null) 'status': status, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + }); + } + + JobsCompanion copyWith({ + Value? id, + Value? projectKey, + Value? issueNumber, + Value? fingerprint, + Value? trigger, + Value? status, + Value? createdAt, + Value? updatedAt, + }) { + return JobsCompanion( + id: id ?? this.id, + projectKey: projectKey ?? this.projectKey, + issueNumber: issueNumber ?? this.issueNumber, + fingerprint: fingerprint ?? this.fingerprint, + trigger: trigger ?? this.trigger, + status: status ?? this.status, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (projectKey.present) { + map['project_key'] = Variable(projectKey.value); + } + if (issueNumber.present) { + map['issue_number'] = Variable(issueNumber.value); + } + if (fingerprint.present) { + map['fingerprint'] = Variable(fingerprint.value); + } + if (trigger.present) { + map['trigger'] = Variable(trigger.value); + } + if (status.present) { + map['status'] = Variable(status.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('JobsCompanion(') + ..write('id: $id, ') + ..write('projectKey: $projectKey, ') + ..write('issueNumber: $issueNumber, ') + ..write('fingerprint: $fingerprint, ') + ..write('trigger: $trigger, ') + ..write('status: $status, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } +} + +class $ExecutionRunsTable extends ExecutionRuns + with TableInfo<$ExecutionRunsTable, ExecutionRun> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $ExecutionRunsTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'PRIMARY KEY AUTOINCREMENT', + ), + ); + static const VerificationMeta _jobIdMeta = const VerificationMeta('jobId'); + @override + late final GeneratedColumn jobId = GeneratedColumn( + 'job_id', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + static const VerificationMeta _responderIdMeta = const VerificationMeta( + 'responderId', + ); + @override + late final GeneratedColumn responderId = GeneratedColumn( + 'responder_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _modeMeta = const VerificationMeta('mode'); + @override + late final GeneratedColumn mode = GeneratedColumn( + 'mode', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _decisionMeta = const VerificationMeta( + 'decision', + ); + @override + late final GeneratedColumn decision = GeneratedColumn( + 'decision', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _successMeta = const VerificationMeta( + 'success', + ); + @override + late final GeneratedColumn success = GeneratedColumn( + 'success', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("success" IN (0, 1))', + ), + ); + static const VerificationMeta _workspacePathMeta = const VerificationMeta( + 'workspacePath', + ); + @override + late final GeneratedColumn workspacePath = GeneratedColumn( + 'workspace_path', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _stdoutMeta = const VerificationMeta('stdout'); + @override + late final GeneratedColumn stdout = GeneratedColumn( + 'stdout', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _stderrMeta = const VerificationMeta('stderr'); + @override + late final GeneratedColumn stderr = GeneratedColumn( + 'stderr', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _createdAtMeta = const VerificationMeta( + 'createdAt', + ); + @override + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + jobId, + responderId, + mode, + decision, + success, + workspacePath, + stdout, + stderr, + createdAt, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'execution_runs'; + @override + VerificationContext validateIntegrity( + Insertable instance, { + bool isInserting = false, + }) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('job_id')) { + context.handle( + _jobIdMeta, + jobId.isAcceptableOrUnknown(data['job_id']!, _jobIdMeta), + ); + } else if (isInserting) { + context.missing(_jobIdMeta); + } + if (data.containsKey('responder_id')) { + context.handle( + _responderIdMeta, + responderId.isAcceptableOrUnknown( + data['responder_id']!, + _responderIdMeta, + ), + ); + } else if (isInserting) { + context.missing(_responderIdMeta); + } + if (data.containsKey('mode')) { + context.handle( + _modeMeta, + mode.isAcceptableOrUnknown(data['mode']!, _modeMeta), + ); + } else if (isInserting) { + context.missing(_modeMeta); + } + if (data.containsKey('decision')) { + context.handle( + _decisionMeta, + decision.isAcceptableOrUnknown(data['decision']!, _decisionMeta), + ); + } else if (isInserting) { + context.missing(_decisionMeta); + } + if (data.containsKey('success')) { + context.handle( + _successMeta, + success.isAcceptableOrUnknown(data['success']!, _successMeta), + ); + } else if (isInserting) { + context.missing(_successMeta); + } + if (data.containsKey('workspace_path')) { + context.handle( + _workspacePathMeta, + workspacePath.isAcceptableOrUnknown( + data['workspace_path']!, + _workspacePathMeta, + ), + ); + } + if (data.containsKey('stdout')) { + context.handle( + _stdoutMeta, + stdout.isAcceptableOrUnknown(data['stdout']!, _stdoutMeta), + ); + } else if (isInserting) { + context.missing(_stdoutMeta); + } + if (data.containsKey('stderr')) { + context.handle( + _stderrMeta, + stderr.isAcceptableOrUnknown(data['stderr']!, _stderrMeta), + ); + } else if (isInserting) { + context.missing(_stderrMeta); + } + if (data.containsKey('created_at')) { + context.handle( + _createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta), + ); + } else if (isInserting) { + context.missing(_createdAtMeta); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + ExecutionRun map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return ExecutionRun( + id: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}id'], + )!, + jobId: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}job_id'], + )!, + responderId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}responder_id'], + )!, + mode: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}mode'], + )!, + decision: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}decision'], + )!, + success: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}success'], + )!, + workspacePath: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}workspace_path'], + ), + stdout: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}stdout'], + )!, + stderr: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}stderr'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}created_at'], + )!, + ); + } + + @override + $ExecutionRunsTable createAlias(String alias) { + return $ExecutionRunsTable(attachedDatabase, alias); + } +} + +class ExecutionRun extends DataClass implements Insertable { + final int id; + final int jobId; + final String responderId; + final String mode; + final String decision; + final bool success; + final String? workspacePath; + final String stdout; + final String stderr; + final String createdAt; + const ExecutionRun({ + required this.id, + required this.jobId, + required this.responderId, + required this.mode, + required this.decision, + required this.success, + this.workspacePath, + required this.stdout, + required this.stderr, + required this.createdAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['job_id'] = Variable(jobId); + map['responder_id'] = Variable(responderId); + map['mode'] = Variable(mode); + map['decision'] = Variable(decision); + map['success'] = Variable(success); + if (!nullToAbsent || workspacePath != null) { + map['workspace_path'] = Variable(workspacePath); + } + map['stdout'] = Variable(stdout); + map['stderr'] = Variable(stderr); + map['created_at'] = Variable(createdAt); + return map; + } + + ExecutionRunsCompanion toCompanion(bool nullToAbsent) { + return ExecutionRunsCompanion( + id: Value(id), + jobId: Value(jobId), + responderId: Value(responderId), + mode: Value(mode), + decision: Value(decision), + success: Value(success), + workspacePath: workspacePath == null && nullToAbsent + ? const Value.absent() + : Value(workspacePath), + stdout: Value(stdout), + stderr: Value(stderr), + createdAt: Value(createdAt), + ); + } + + factory ExecutionRun.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return ExecutionRun( + id: serializer.fromJson(json['id']), + jobId: serializer.fromJson(json['jobId']), + responderId: serializer.fromJson(json['responderId']), + mode: serializer.fromJson(json['mode']), + decision: serializer.fromJson(json['decision']), + success: serializer.fromJson(json['success']), + workspacePath: serializer.fromJson(json['workspacePath']), + stdout: serializer.fromJson(json['stdout']), + stderr: serializer.fromJson(json['stderr']), + createdAt: serializer.fromJson(json['createdAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'jobId': serializer.toJson(jobId), + 'responderId': serializer.toJson(responderId), + 'mode': serializer.toJson(mode), + 'decision': serializer.toJson(decision), + 'success': serializer.toJson(success), + 'workspacePath': serializer.toJson(workspacePath), + 'stdout': serializer.toJson(stdout), + 'stderr': serializer.toJson(stderr), + 'createdAt': serializer.toJson(createdAt), + }; + } + + ExecutionRun copyWith({ + int? id, + int? jobId, + String? responderId, + String? mode, + String? decision, + bool? success, + Value workspacePath = const Value.absent(), + String? stdout, + String? stderr, + String? createdAt, + }) => ExecutionRun( + id: id ?? this.id, + jobId: jobId ?? this.jobId, + responderId: responderId ?? this.responderId, + mode: mode ?? this.mode, + decision: decision ?? this.decision, + success: success ?? this.success, + workspacePath: workspacePath.present + ? workspacePath.value + : this.workspacePath, + stdout: stdout ?? this.stdout, + stderr: stderr ?? this.stderr, + createdAt: createdAt ?? this.createdAt, + ); + ExecutionRun copyWithCompanion(ExecutionRunsCompanion data) { + return ExecutionRun( + id: data.id.present ? data.id.value : this.id, + jobId: data.jobId.present ? data.jobId.value : this.jobId, + responderId: data.responderId.present + ? data.responderId.value + : this.responderId, + mode: data.mode.present ? data.mode.value : this.mode, + decision: data.decision.present ? data.decision.value : this.decision, + success: data.success.present ? data.success.value : this.success, + workspacePath: data.workspacePath.present + ? data.workspacePath.value + : this.workspacePath, + stdout: data.stdout.present ? data.stdout.value : this.stdout, + stderr: data.stderr.present ? data.stderr.value : this.stderr, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + ); + } + + @override + String toString() { + return (StringBuffer('ExecutionRun(') + ..write('id: $id, ') + ..write('jobId: $jobId, ') + ..write('responderId: $responderId, ') + ..write('mode: $mode, ') + ..write('decision: $decision, ') + ..write('success: $success, ') + ..write('workspacePath: $workspacePath, ') + ..write('stdout: $stdout, ') + ..write('stderr: $stderr, ') + ..write('createdAt: $createdAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + jobId, + responderId, + mode, + decision, + success, + workspacePath, + stdout, + stderr, + createdAt, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is ExecutionRun && + other.id == this.id && + other.jobId == this.jobId && + other.responderId == this.responderId && + other.mode == this.mode && + other.decision == this.decision && + other.success == this.success && + other.workspacePath == this.workspacePath && + other.stdout == this.stdout && + other.stderr == this.stderr && + other.createdAt == this.createdAt); +} + +class ExecutionRunsCompanion extends UpdateCompanion { + final Value id; + final Value jobId; + final Value responderId; + final Value mode; + final Value decision; + final Value success; + final Value workspacePath; + final Value stdout; + final Value stderr; + final Value createdAt; + const ExecutionRunsCompanion({ + this.id = const Value.absent(), + this.jobId = const Value.absent(), + this.responderId = const Value.absent(), + this.mode = const Value.absent(), + this.decision = const Value.absent(), + this.success = const Value.absent(), + this.workspacePath = const Value.absent(), + this.stdout = const Value.absent(), + this.stderr = const Value.absent(), + this.createdAt = const Value.absent(), + }); + ExecutionRunsCompanion.insert({ + this.id = const Value.absent(), + required int jobId, + required String responderId, + required String mode, + required String decision, + required bool success, + this.workspacePath = const Value.absent(), + required String stdout, + required String stderr, + required String createdAt, + }) : jobId = Value(jobId), + responderId = Value(responderId), + mode = Value(mode), + decision = Value(decision), + success = Value(success), + stdout = Value(stdout), + stderr = Value(stderr), + createdAt = Value(createdAt); + static Insertable custom({ + Expression? id, + Expression? jobId, + Expression? responderId, + Expression? mode, + Expression? decision, + Expression? success, + Expression? workspacePath, + Expression? stdout, + Expression? stderr, + Expression? createdAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (jobId != null) 'job_id': jobId, + if (responderId != null) 'responder_id': responderId, + if (mode != null) 'mode': mode, + if (decision != null) 'decision': decision, + if (success != null) 'success': success, + if (workspacePath != null) 'workspace_path': workspacePath, + if (stdout != null) 'stdout': stdout, + if (stderr != null) 'stderr': stderr, + if (createdAt != null) 'created_at': createdAt, + }); + } + + ExecutionRunsCompanion copyWith({ + Value? id, + Value? jobId, + Value? responderId, + Value? mode, + Value? decision, + Value? success, + Value? workspacePath, + Value? stdout, + Value? stderr, + Value? createdAt, + }) { + return ExecutionRunsCompanion( + id: id ?? this.id, + jobId: jobId ?? this.jobId, + responderId: responderId ?? this.responderId, + mode: mode ?? this.mode, + decision: decision ?? this.decision, + success: success ?? this.success, + workspacePath: workspacePath ?? this.workspacePath, + stdout: stdout ?? this.stdout, + stderr: stderr ?? this.stderr, + createdAt: createdAt ?? this.createdAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (jobId.present) { + map['job_id'] = Variable(jobId.value); + } + if (responderId.present) { + map['responder_id'] = Variable(responderId.value); + } + if (mode.present) { + map['mode'] = Variable(mode.value); + } + if (decision.present) { + map['decision'] = Variable(decision.value); + } + if (success.present) { + map['success'] = Variable(success.value); + } + if (workspacePath.present) { + map['workspace_path'] = Variable(workspacePath.value); + } + if (stdout.present) { + map['stdout'] = Variable(stdout.value); + } + if (stderr.present) { + map['stderr'] = Variable(stderr.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('ExecutionRunsCompanion(') + ..write('id: $id, ') + ..write('jobId: $jobId, ') + ..write('responderId: $responderId, ') + ..write('mode: $mode, ') + ..write('decision: $decision, ') + ..write('success: $success, ') + ..write('workspacePath: $workspacePath, ') + ..write('stdout: $stdout, ') + ..write('stderr: $stderr, ') + ..write('createdAt: $createdAt') + ..write(')')) + .toString(); + } +} + +class $CommentsTable extends Comments with TableInfo<$CommentsTable, Comment> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $CommentsTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'PRIMARY KEY AUTOINCREMENT', + ), + ); + static const VerificationMeta _projectKeyMeta = const VerificationMeta( + 'projectKey', + ); + @override + late final GeneratedColumn projectKey = GeneratedColumn( + 'project_key', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _issueNumberMeta = const VerificationMeta( + 'issueNumber', + ); + @override + late final GeneratedColumn issueNumber = GeneratedColumn( + 'issue_number', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + static const VerificationMeta _fingerprintMeta = const VerificationMeta( + 'fingerprint', + ); + @override + late final GeneratedColumn fingerprint = GeneratedColumn( + 'fingerprint', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _githubCommentIdMeta = const VerificationMeta( + 'githubCommentId', + ); + @override + late final GeneratedColumn githubCommentId = GeneratedColumn( + 'github_comment_id', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + static const VerificationMeta _githubCommentUrlMeta = const VerificationMeta( + 'githubCommentUrl', + ); + @override + late final GeneratedColumn githubCommentUrl = GeneratedColumn( + 'github_comment_url', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _bodyMeta = const VerificationMeta('body'); + @override + late final GeneratedColumn body = GeneratedColumn( + 'body', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _createdAtMeta = const VerificationMeta( + 'createdAt', + ); + @override + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + projectKey, + issueNumber, + fingerprint, + githubCommentId, + githubCommentUrl, + body, + createdAt, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'comments'; + @override + VerificationContext validateIntegrity( + Insertable instance, { + bool isInserting = false, + }) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('project_key')) { + context.handle( + _projectKeyMeta, + projectKey.isAcceptableOrUnknown(data['project_key']!, _projectKeyMeta), + ); + } else if (isInserting) { + context.missing(_projectKeyMeta); + } + if (data.containsKey('issue_number')) { + context.handle( + _issueNumberMeta, + issueNumber.isAcceptableOrUnknown( + data['issue_number']!, + _issueNumberMeta, + ), + ); + } else if (isInserting) { + context.missing(_issueNumberMeta); + } + if (data.containsKey('fingerprint')) { + context.handle( + _fingerprintMeta, + fingerprint.isAcceptableOrUnknown( + data['fingerprint']!, + _fingerprintMeta, + ), + ); + } else if (isInserting) { + context.missing(_fingerprintMeta); + } + if (data.containsKey('github_comment_id')) { + context.handle( + _githubCommentIdMeta, + githubCommentId.isAcceptableOrUnknown( + data['github_comment_id']!, + _githubCommentIdMeta, + ), + ); + } else if (isInserting) { + context.missing(_githubCommentIdMeta); + } + if (data.containsKey('github_comment_url')) { + context.handle( + _githubCommentUrlMeta, + githubCommentUrl.isAcceptableOrUnknown( + data['github_comment_url']!, + _githubCommentUrlMeta, + ), + ); + } else if (isInserting) { + context.missing(_githubCommentUrlMeta); + } + if (data.containsKey('body')) { + context.handle( + _bodyMeta, + body.isAcceptableOrUnknown(data['body']!, _bodyMeta), + ); + } else if (isInserting) { + context.missing(_bodyMeta); + } + if (data.containsKey('created_at')) { + context.handle( + _createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta), + ); + } else if (isInserting) { + context.missing(_createdAtMeta); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + Comment map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return Comment( + id: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}id'], + )!, + projectKey: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}project_key'], + )!, + issueNumber: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}issue_number'], + )!, + fingerprint: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}fingerprint'], + )!, + githubCommentId: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}github_comment_id'], + )!, + githubCommentUrl: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}github_comment_url'], + )!, + body: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}body'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}created_at'], + )!, + ); + } + + @override + $CommentsTable createAlias(String alias) { + return $CommentsTable(attachedDatabase, alias); + } +} + +class Comment extends DataClass implements Insertable { + final int id; + final String projectKey; + final int issueNumber; + final String fingerprint; + final int githubCommentId; + final String githubCommentUrl; + final String body; + final String createdAt; + const Comment({ + required this.id, + required this.projectKey, + required this.issueNumber, + required this.fingerprint, + required this.githubCommentId, + required this.githubCommentUrl, + required this.body, + required this.createdAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['project_key'] = Variable(projectKey); + map['issue_number'] = Variable(issueNumber); + map['fingerprint'] = Variable(fingerprint); + map['github_comment_id'] = Variable(githubCommentId); + map['github_comment_url'] = Variable(githubCommentUrl); + map['body'] = Variable(body); + map['created_at'] = Variable(createdAt); + return map; + } + + CommentsCompanion toCompanion(bool nullToAbsent) { + return CommentsCompanion( + id: Value(id), + projectKey: Value(projectKey), + issueNumber: Value(issueNumber), + fingerprint: Value(fingerprint), + githubCommentId: Value(githubCommentId), + githubCommentUrl: Value(githubCommentUrl), + body: Value(body), + createdAt: Value(createdAt), + ); + } + + factory Comment.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return Comment( + id: serializer.fromJson(json['id']), + projectKey: serializer.fromJson(json['projectKey']), + issueNumber: serializer.fromJson(json['issueNumber']), + fingerprint: serializer.fromJson(json['fingerprint']), + githubCommentId: serializer.fromJson(json['githubCommentId']), + githubCommentUrl: serializer.fromJson(json['githubCommentUrl']), + body: serializer.fromJson(json['body']), + createdAt: serializer.fromJson(json['createdAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'projectKey': serializer.toJson(projectKey), + 'issueNumber': serializer.toJson(issueNumber), + 'fingerprint': serializer.toJson(fingerprint), + 'githubCommentId': serializer.toJson(githubCommentId), + 'githubCommentUrl': serializer.toJson(githubCommentUrl), + 'body': serializer.toJson(body), + 'createdAt': serializer.toJson(createdAt), + }; + } + + Comment copyWith({ + int? id, + String? projectKey, + int? issueNumber, + String? fingerprint, + int? githubCommentId, + String? githubCommentUrl, + String? body, + String? createdAt, + }) => Comment( + id: id ?? this.id, + projectKey: projectKey ?? this.projectKey, + issueNumber: issueNumber ?? this.issueNumber, + fingerprint: fingerprint ?? this.fingerprint, + githubCommentId: githubCommentId ?? this.githubCommentId, + githubCommentUrl: githubCommentUrl ?? this.githubCommentUrl, + body: body ?? this.body, + createdAt: createdAt ?? this.createdAt, + ); + Comment copyWithCompanion(CommentsCompanion data) { + return Comment( + id: data.id.present ? data.id.value : this.id, + projectKey: data.projectKey.present + ? data.projectKey.value + : this.projectKey, + issueNumber: data.issueNumber.present + ? data.issueNumber.value + : this.issueNumber, + fingerprint: data.fingerprint.present + ? data.fingerprint.value + : this.fingerprint, + githubCommentId: data.githubCommentId.present + ? data.githubCommentId.value + : this.githubCommentId, + githubCommentUrl: data.githubCommentUrl.present + ? data.githubCommentUrl.value + : this.githubCommentUrl, + body: data.body.present ? data.body.value : this.body, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + ); + } + + @override + String toString() { + return (StringBuffer('Comment(') + ..write('id: $id, ') + ..write('projectKey: $projectKey, ') + ..write('issueNumber: $issueNumber, ') + ..write('fingerprint: $fingerprint, ') + ..write('githubCommentId: $githubCommentId, ') + ..write('githubCommentUrl: $githubCommentUrl, ') + ..write('body: $body, ') + ..write('createdAt: $createdAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + projectKey, + issueNumber, + fingerprint, + githubCommentId, + githubCommentUrl, + body, + createdAt, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is Comment && + other.id == this.id && + other.projectKey == this.projectKey && + other.issueNumber == this.issueNumber && + other.fingerprint == this.fingerprint && + other.githubCommentId == this.githubCommentId && + other.githubCommentUrl == this.githubCommentUrl && + other.body == this.body && + other.createdAt == this.createdAt); +} + +class CommentsCompanion extends UpdateCompanion { + final Value id; + final Value projectKey; + final Value issueNumber; + final Value fingerprint; + final Value githubCommentId; + final Value githubCommentUrl; + final Value body; + final Value createdAt; + const CommentsCompanion({ + this.id = const Value.absent(), + this.projectKey = const Value.absent(), + this.issueNumber = const Value.absent(), + this.fingerprint = const Value.absent(), + this.githubCommentId = const Value.absent(), + this.githubCommentUrl = const Value.absent(), + this.body = const Value.absent(), + this.createdAt = const Value.absent(), + }); + CommentsCompanion.insert({ + this.id = const Value.absent(), + required String projectKey, + required int issueNumber, + required String fingerprint, + required int githubCommentId, + required String githubCommentUrl, + required String body, + required String createdAt, + }) : projectKey = Value(projectKey), + issueNumber = Value(issueNumber), + fingerprint = Value(fingerprint), + githubCommentId = Value(githubCommentId), + githubCommentUrl = Value(githubCommentUrl), + body = Value(body), + createdAt = Value(createdAt); + static Insertable custom({ + Expression? id, + Expression? projectKey, + Expression? issueNumber, + Expression? fingerprint, + Expression? githubCommentId, + Expression? githubCommentUrl, + Expression? body, + Expression? createdAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (projectKey != null) 'project_key': projectKey, + if (issueNumber != null) 'issue_number': issueNumber, + if (fingerprint != null) 'fingerprint': fingerprint, + if (githubCommentId != null) 'github_comment_id': githubCommentId, + if (githubCommentUrl != null) 'github_comment_url': githubCommentUrl, + if (body != null) 'body': body, + if (createdAt != null) 'created_at': createdAt, + }); + } + + CommentsCompanion copyWith({ + Value? id, + Value? projectKey, + Value? issueNumber, + Value? fingerprint, + Value? githubCommentId, + Value? githubCommentUrl, + Value? body, + Value? createdAt, + }) { + return CommentsCompanion( + id: id ?? this.id, + projectKey: projectKey ?? this.projectKey, + issueNumber: issueNumber ?? this.issueNumber, + fingerprint: fingerprint ?? this.fingerprint, + githubCommentId: githubCommentId ?? this.githubCommentId, + githubCommentUrl: githubCommentUrl ?? this.githubCommentUrl, + body: body ?? this.body, + createdAt: createdAt ?? this.createdAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (projectKey.present) { + map['project_key'] = Variable(projectKey.value); + } + if (issueNumber.present) { + map['issue_number'] = Variable(issueNumber.value); + } + if (fingerprint.present) { + map['fingerprint'] = Variable(fingerprint.value); + } + if (githubCommentId.present) { + map['github_comment_id'] = Variable(githubCommentId.value); + } + if (githubCommentUrl.present) { + map['github_comment_url'] = Variable(githubCommentUrl.value); + } + if (body.present) { + map['body'] = Variable(body.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('CommentsCompanion(') + ..write('id: $id, ') + ..write('projectKey: $projectKey, ') + ..write('issueNumber: $issueNumber, ') + ..write('fingerprint: $fingerprint, ') + ..write('githubCommentId: $githubCommentId, ') + ..write('githubCommentUrl: $githubCommentUrl, ') + ..write('body: $body, ') + ..write('createdAt: $createdAt') + ..write(')')) + .toString(); + } +} + +class $PollRunsTable extends PollRuns with TableInfo<$PollRunsTable, PollRun> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $PollRunsTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'PRIMARY KEY AUTOINCREMENT', + ), + ); + static const VerificationMeta _projectKeyMeta = const VerificationMeta( + 'projectKey', + ); + @override + late final GeneratedColumn projectKey = GeneratedColumn( + 'project_key', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _startedAtMeta = const VerificationMeta( + 'startedAt', + ); + @override + late final GeneratedColumn startedAt = GeneratedColumn( + 'started_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _finishedAtMeta = const VerificationMeta( + 'finishedAt', + ); + @override + late final GeneratedColumn finishedAt = GeneratedColumn( + 'finished_at', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _successMeta = const VerificationMeta( + 'success', + ); + @override + late final GeneratedColumn success = GeneratedColumn( + 'success', + aliasedName, + true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("success" IN (0, 1))', + ), + ); + static const VerificationMeta _errorMeta = const VerificationMeta('error'); + @override + late final GeneratedColumn error = GeneratedColumn( + 'error', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + projectKey, + startedAt, + finishedAt, + success, + error, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'poll_runs'; + @override + VerificationContext validateIntegrity( + Insertable instance, { + bool isInserting = false, + }) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('project_key')) { + context.handle( + _projectKeyMeta, + projectKey.isAcceptableOrUnknown(data['project_key']!, _projectKeyMeta), + ); + } else if (isInserting) { + context.missing(_projectKeyMeta); + } + if (data.containsKey('started_at')) { + context.handle( + _startedAtMeta, + startedAt.isAcceptableOrUnknown(data['started_at']!, _startedAtMeta), + ); + } else if (isInserting) { + context.missing(_startedAtMeta); + } + if (data.containsKey('finished_at')) { + context.handle( + _finishedAtMeta, + finishedAt.isAcceptableOrUnknown(data['finished_at']!, _finishedAtMeta), + ); + } + if (data.containsKey('success')) { + context.handle( + _successMeta, + success.isAcceptableOrUnknown(data['success']!, _successMeta), + ); + } + if (data.containsKey('error')) { + context.handle( + _errorMeta, + error.isAcceptableOrUnknown(data['error']!, _errorMeta), + ); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + PollRun map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PollRun( + id: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}id'], + )!, + projectKey: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}project_key'], + )!, + startedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}started_at'], + )!, + finishedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}finished_at'], + ), + success: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}success'], + ), + error: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}error'], + ), + ); + } + + @override + $PollRunsTable createAlias(String alias) { + return $PollRunsTable(attachedDatabase, alias); + } +} + +class PollRun extends DataClass implements Insertable { + final int id; + final String projectKey; + final String startedAt; + final String? finishedAt; + final bool? success; + final String? error; + const PollRun({ + required this.id, + required this.projectKey, + required this.startedAt, + this.finishedAt, + this.success, + this.error, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['project_key'] = Variable(projectKey); + map['started_at'] = Variable(startedAt); + if (!nullToAbsent || finishedAt != null) { + map['finished_at'] = Variable(finishedAt); + } + if (!nullToAbsent || success != null) { + map['success'] = Variable(success); + } + if (!nullToAbsent || error != null) { + map['error'] = Variable(error); + } + return map; + } + + PollRunsCompanion toCompanion(bool nullToAbsent) { + return PollRunsCompanion( + id: Value(id), + projectKey: Value(projectKey), + startedAt: Value(startedAt), + finishedAt: finishedAt == null && nullToAbsent + ? const Value.absent() + : Value(finishedAt), + success: success == null && nullToAbsent + ? const Value.absent() + : Value(success), + error: error == null && nullToAbsent + ? const Value.absent() + : Value(error), + ); + } + + factory PollRun.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PollRun( + id: serializer.fromJson(json['id']), + projectKey: serializer.fromJson(json['projectKey']), + startedAt: serializer.fromJson(json['startedAt']), + finishedAt: serializer.fromJson(json['finishedAt']), + success: serializer.fromJson(json['success']), + error: serializer.fromJson(json['error']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'projectKey': serializer.toJson(projectKey), + 'startedAt': serializer.toJson(startedAt), + 'finishedAt': serializer.toJson(finishedAt), + 'success': serializer.toJson(success), + 'error': serializer.toJson(error), + }; + } + + PollRun copyWith({ + int? id, + String? projectKey, + String? startedAt, + Value finishedAt = const Value.absent(), + Value success = const Value.absent(), + Value error = const Value.absent(), + }) => PollRun( + id: id ?? this.id, + projectKey: projectKey ?? this.projectKey, + startedAt: startedAt ?? this.startedAt, + finishedAt: finishedAt.present ? finishedAt.value : this.finishedAt, + success: success.present ? success.value : this.success, + error: error.present ? error.value : this.error, + ); + PollRun copyWithCompanion(PollRunsCompanion data) { + return PollRun( + id: data.id.present ? data.id.value : this.id, + projectKey: data.projectKey.present + ? data.projectKey.value + : this.projectKey, + startedAt: data.startedAt.present ? data.startedAt.value : this.startedAt, + finishedAt: data.finishedAt.present + ? data.finishedAt.value + : this.finishedAt, + success: data.success.present ? data.success.value : this.success, + error: data.error.present ? data.error.value : this.error, + ); + } + + @override + String toString() { + return (StringBuffer('PollRun(') + ..write('id: $id, ') + ..write('projectKey: $projectKey, ') + ..write('startedAt: $startedAt, ') + ..write('finishedAt: $finishedAt, ') + ..write('success: $success, ') + ..write('error: $error') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(id, projectKey, startedAt, finishedAt, success, error); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PollRun && + other.id == this.id && + other.projectKey == this.projectKey && + other.startedAt == this.startedAt && + other.finishedAt == this.finishedAt && + other.success == this.success && + other.error == this.error); +} + +class PollRunsCompanion extends UpdateCompanion { + final Value id; + final Value projectKey; + final Value startedAt; + final Value finishedAt; + final Value success; + final Value error; + const PollRunsCompanion({ + this.id = const Value.absent(), + this.projectKey = const Value.absent(), + this.startedAt = const Value.absent(), + this.finishedAt = const Value.absent(), + this.success = const Value.absent(), + this.error = const Value.absent(), + }); + PollRunsCompanion.insert({ + this.id = const Value.absent(), + required String projectKey, + required String startedAt, + this.finishedAt = const Value.absent(), + this.success = const Value.absent(), + this.error = const Value.absent(), + }) : projectKey = Value(projectKey), + startedAt = Value(startedAt); + static Insertable custom({ + Expression? id, + Expression? projectKey, + Expression? startedAt, + Expression? finishedAt, + Expression? success, + Expression? error, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (projectKey != null) 'project_key': projectKey, + if (startedAt != null) 'started_at': startedAt, + if (finishedAt != null) 'finished_at': finishedAt, + if (success != null) 'success': success, + if (error != null) 'error': error, + }); + } + + PollRunsCompanion copyWith({ + Value? id, + Value? projectKey, + Value? startedAt, + Value? finishedAt, + Value? success, + Value? error, + }) { + return PollRunsCompanion( + id: id ?? this.id, + projectKey: projectKey ?? this.projectKey, + startedAt: startedAt ?? this.startedAt, + finishedAt: finishedAt ?? this.finishedAt, + success: success ?? this.success, + error: error ?? this.error, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (projectKey.present) { + map['project_key'] = Variable(projectKey.value); + } + if (startedAt.present) { + map['started_at'] = Variable(startedAt.value); + } + if (finishedAt.present) { + map['finished_at'] = Variable(finishedAt.value); + } + if (success.present) { + map['success'] = Variable(success.value); + } + if (error.present) { + map['error'] = Variable(error.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PollRunsCompanion(') + ..write('id: $id, ') + ..write('projectKey: $projectKey, ') + ..write('startedAt: $startedAt, ') + ..write('finishedAt: $finishedAt, ') + ..write('success: $success, ') + ..write('error: $error') + ..write(')')) + .toString(); + } +} + +abstract class _$AppDatabase extends GeneratedDatabase { + _$AppDatabase(QueryExecutor e) : super(e); + $AppDatabaseManager get managers => $AppDatabaseManager(this); + late final $ProjectStatesTable projectStates = $ProjectStatesTable(this); + late final $IssueThreadStatesTable issueThreadStates = + $IssueThreadStatesTable(this); + late final $JobsTable jobs = $JobsTable(this); + late final $ExecutionRunsTable executionRuns = $ExecutionRunsTable(this); + late final $CommentsTable comments = $CommentsTable(this); + late final $PollRunsTable pollRuns = $PollRunsTable(this); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [ + projectStates, + issueThreadStates, + jobs, + executionRuns, + comments, + pollRuns, + ]; +} + +typedef $$ProjectStatesTableCreateCompanionBuilder = + ProjectStatesCompanion Function({ + required String projectKey, + required String repo, + Value lastSeenUpdatedAt, + required String updatedAt, + Value rowid, + }); +typedef $$ProjectStatesTableUpdateCompanionBuilder = + ProjectStatesCompanion Function({ + Value projectKey, + Value repo, + Value lastSeenUpdatedAt, + Value updatedAt, + Value rowid, + }); + +class $$ProjectStatesTableFilterComposer + extends Composer<_$AppDatabase, $ProjectStatesTable> { + $$ProjectStatesTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get projectKey => $composableBuilder( + column: $table.projectKey, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get repo => $composableBuilder( + column: $table.repo, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get lastSeenUpdatedAt => $composableBuilder( + column: $table.lastSeenUpdatedAt, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get updatedAt => $composableBuilder( + column: $table.updatedAt, + builder: (column) => ColumnFilters(column), + ); +} + +class $$ProjectStatesTableOrderingComposer + extends Composer<_$AppDatabase, $ProjectStatesTable> { + $$ProjectStatesTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get projectKey => $composableBuilder( + column: $table.projectKey, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get repo => $composableBuilder( + column: $table.repo, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get lastSeenUpdatedAt => $composableBuilder( + column: $table.lastSeenUpdatedAt, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get updatedAt => $composableBuilder( + column: $table.updatedAt, + builder: (column) => ColumnOrderings(column), + ); +} + +class $$ProjectStatesTableAnnotationComposer + extends Composer<_$AppDatabase, $ProjectStatesTable> { + $$ProjectStatesTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get projectKey => $composableBuilder( + column: $table.projectKey, + builder: (column) => column, + ); + + GeneratedColumn get repo => + $composableBuilder(column: $table.repo, builder: (column) => column); + + GeneratedColumn get lastSeenUpdatedAt => $composableBuilder( + column: $table.lastSeenUpdatedAt, + builder: (column) => column, + ); + + GeneratedColumn get updatedAt => + $composableBuilder(column: $table.updatedAt, builder: (column) => column); +} + +class $$ProjectStatesTableTableManager + extends + RootTableManager< + _$AppDatabase, + $ProjectStatesTable, + ProjectState, + $$ProjectStatesTableFilterComposer, + $$ProjectStatesTableOrderingComposer, + $$ProjectStatesTableAnnotationComposer, + $$ProjectStatesTableCreateCompanionBuilder, + $$ProjectStatesTableUpdateCompanionBuilder, + ( + ProjectState, + BaseReferences<_$AppDatabase, $ProjectStatesTable, ProjectState>, + ), + ProjectState, + PrefetchHooks Function() + > { + $$ProjectStatesTableTableManager(_$AppDatabase db, $ProjectStatesTable table) + : super( + TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$ProjectStatesTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$ProjectStatesTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$ProjectStatesTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value projectKey = const Value.absent(), + Value repo = const Value.absent(), + Value lastSeenUpdatedAt = const Value.absent(), + Value updatedAt = const Value.absent(), + Value rowid = const Value.absent(), + }) => ProjectStatesCompanion( + projectKey: projectKey, + repo: repo, + lastSeenUpdatedAt: lastSeenUpdatedAt, + updatedAt: updatedAt, + rowid: rowid, + ), + createCompanionCallback: + ({ + required String projectKey, + required String repo, + Value lastSeenUpdatedAt = const Value.absent(), + required String updatedAt, + Value rowid = const Value.absent(), + }) => ProjectStatesCompanion.insert( + projectKey: projectKey, + repo: repo, + lastSeenUpdatedAt: lastSeenUpdatedAt, + updatedAt: updatedAt, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + ), + ); +} + +typedef $$ProjectStatesTableProcessedTableManager = + ProcessedTableManager< + _$AppDatabase, + $ProjectStatesTable, + ProjectState, + $$ProjectStatesTableFilterComposer, + $$ProjectStatesTableOrderingComposer, + $$ProjectStatesTableAnnotationComposer, + $$ProjectStatesTableCreateCompanionBuilder, + $$ProjectStatesTableUpdateCompanionBuilder, + ( + ProjectState, + BaseReferences<_$AppDatabase, $ProjectStatesTable, ProjectState>, + ), + ProjectState, + PrefetchHooks Function() + >; +typedef $$IssueThreadStatesTableCreateCompanionBuilder = + IssueThreadStatesCompanion Function({ + Value id, + required String projectKey, + required int issueNumber, + required String fingerprint, + Value lastEvaluatedFingerprint, + Value lastDecision, + Value lastCommentId, + Value lastCommentUrl, + required String updatedAt, + }); +typedef $$IssueThreadStatesTableUpdateCompanionBuilder = + IssueThreadStatesCompanion Function({ + Value id, + Value projectKey, + Value issueNumber, + Value fingerprint, + Value lastEvaluatedFingerprint, + Value lastDecision, + Value lastCommentId, + Value lastCommentUrl, + Value updatedAt, + }); + +class $$IssueThreadStatesTableFilterComposer + extends Composer<_$AppDatabase, $IssueThreadStatesTable> { + $$IssueThreadStatesTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get projectKey => $composableBuilder( + column: $table.projectKey, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get issueNumber => $composableBuilder( + column: $table.issueNumber, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get fingerprint => $composableBuilder( + column: $table.fingerprint, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get lastEvaluatedFingerprint => $composableBuilder( + column: $table.lastEvaluatedFingerprint, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get lastDecision => $composableBuilder( + column: $table.lastDecision, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get lastCommentId => $composableBuilder( + column: $table.lastCommentId, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get lastCommentUrl => $composableBuilder( + column: $table.lastCommentUrl, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get updatedAt => $composableBuilder( + column: $table.updatedAt, + builder: (column) => ColumnFilters(column), + ); +} + +class $$IssueThreadStatesTableOrderingComposer + extends Composer<_$AppDatabase, $IssueThreadStatesTable> { + $$IssueThreadStatesTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get projectKey => $composableBuilder( + column: $table.projectKey, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get issueNumber => $composableBuilder( + column: $table.issueNumber, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get fingerprint => $composableBuilder( + column: $table.fingerprint, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get lastEvaluatedFingerprint => $composableBuilder( + column: $table.lastEvaluatedFingerprint, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get lastDecision => $composableBuilder( + column: $table.lastDecision, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get lastCommentId => $composableBuilder( + column: $table.lastCommentId, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get lastCommentUrl => $composableBuilder( + column: $table.lastCommentUrl, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get updatedAt => $composableBuilder( + column: $table.updatedAt, + builder: (column) => ColumnOrderings(column), + ); +} + +class $$IssueThreadStatesTableAnnotationComposer + extends Composer<_$AppDatabase, $IssueThreadStatesTable> { + $$IssueThreadStatesTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get projectKey => $composableBuilder( + column: $table.projectKey, + builder: (column) => column, + ); + + GeneratedColumn get issueNumber => $composableBuilder( + column: $table.issueNumber, + builder: (column) => column, + ); + + GeneratedColumn get fingerprint => $composableBuilder( + column: $table.fingerprint, + builder: (column) => column, + ); + + GeneratedColumn get lastEvaluatedFingerprint => $composableBuilder( + column: $table.lastEvaluatedFingerprint, + builder: (column) => column, + ); + + GeneratedColumn get lastDecision => $composableBuilder( + column: $table.lastDecision, + builder: (column) => column, + ); + + GeneratedColumn get lastCommentId => $composableBuilder( + column: $table.lastCommentId, + builder: (column) => column, + ); + + GeneratedColumn get lastCommentUrl => $composableBuilder( + column: $table.lastCommentUrl, + builder: (column) => column, + ); + + GeneratedColumn get updatedAt => + $composableBuilder(column: $table.updatedAt, builder: (column) => column); +} + +class $$IssueThreadStatesTableTableManager + extends + RootTableManager< + _$AppDatabase, + $IssueThreadStatesTable, + IssueThreadState, + $$IssueThreadStatesTableFilterComposer, + $$IssueThreadStatesTableOrderingComposer, + $$IssueThreadStatesTableAnnotationComposer, + $$IssueThreadStatesTableCreateCompanionBuilder, + $$IssueThreadStatesTableUpdateCompanionBuilder, + ( + IssueThreadState, + BaseReferences< + _$AppDatabase, + $IssueThreadStatesTable, + IssueThreadState + >, + ), + IssueThreadState, + PrefetchHooks Function() + > { + $$IssueThreadStatesTableTableManager( + _$AppDatabase db, + $IssueThreadStatesTable table, + ) : super( + TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$IssueThreadStatesTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$IssueThreadStatesTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$IssueThreadStatesTableAnnotationComposer( + $db: db, + $table: table, + ), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value projectKey = const Value.absent(), + Value issueNumber = const Value.absent(), + Value fingerprint = const Value.absent(), + Value lastEvaluatedFingerprint = const Value.absent(), + Value lastDecision = const Value.absent(), + Value lastCommentId = const Value.absent(), + Value lastCommentUrl = const Value.absent(), + Value updatedAt = const Value.absent(), + }) => IssueThreadStatesCompanion( + id: id, + projectKey: projectKey, + issueNumber: issueNumber, + fingerprint: fingerprint, + lastEvaluatedFingerprint: lastEvaluatedFingerprint, + lastDecision: lastDecision, + lastCommentId: lastCommentId, + lastCommentUrl: lastCommentUrl, + updatedAt: updatedAt, + ), + createCompanionCallback: + ({ + Value id = const Value.absent(), + required String projectKey, + required int issueNumber, + required String fingerprint, + Value lastEvaluatedFingerprint = const Value.absent(), + Value lastDecision = const Value.absent(), + Value lastCommentId = const Value.absent(), + Value lastCommentUrl = const Value.absent(), + required String updatedAt, + }) => IssueThreadStatesCompanion.insert( + id: id, + projectKey: projectKey, + issueNumber: issueNumber, + fingerprint: fingerprint, + lastEvaluatedFingerprint: lastEvaluatedFingerprint, + lastDecision: lastDecision, + lastCommentId: lastCommentId, + lastCommentUrl: lastCommentUrl, + updatedAt: updatedAt, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + ), + ); +} + +typedef $$IssueThreadStatesTableProcessedTableManager = + ProcessedTableManager< + _$AppDatabase, + $IssueThreadStatesTable, + IssueThreadState, + $$IssueThreadStatesTableFilterComposer, + $$IssueThreadStatesTableOrderingComposer, + $$IssueThreadStatesTableAnnotationComposer, + $$IssueThreadStatesTableCreateCompanionBuilder, + $$IssueThreadStatesTableUpdateCompanionBuilder, + ( + IssueThreadState, + BaseReferences< + _$AppDatabase, + $IssueThreadStatesTable, + IssueThreadState + >, + ), + IssueThreadState, + PrefetchHooks Function() + >; +typedef $$JobsTableCreateCompanionBuilder = + JobsCompanion Function({ + Value id, + required String projectKey, + required int issueNumber, + required String fingerprint, + required String trigger, + required String status, + required String createdAt, + required String updatedAt, + }); +typedef $$JobsTableUpdateCompanionBuilder = + JobsCompanion Function({ + Value id, + Value projectKey, + Value issueNumber, + Value fingerprint, + Value trigger, + Value status, + Value createdAt, + Value updatedAt, + }); + +class $$JobsTableFilterComposer extends Composer<_$AppDatabase, $JobsTable> { + $$JobsTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get projectKey => $composableBuilder( + column: $table.projectKey, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get issueNumber => $composableBuilder( + column: $table.issueNumber, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get fingerprint => $composableBuilder( + column: $table.fingerprint, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get trigger => $composableBuilder( + column: $table.trigger, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get status => $composableBuilder( + column: $table.status, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get updatedAt => $composableBuilder( + column: $table.updatedAt, + builder: (column) => ColumnFilters(column), + ); +} + +class $$JobsTableOrderingComposer extends Composer<_$AppDatabase, $JobsTable> { + $$JobsTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get projectKey => $composableBuilder( + column: $table.projectKey, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get issueNumber => $composableBuilder( + column: $table.issueNumber, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get fingerprint => $composableBuilder( + column: $table.fingerprint, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get trigger => $composableBuilder( + column: $table.trigger, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get status => $composableBuilder( + column: $table.status, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get updatedAt => $composableBuilder( + column: $table.updatedAt, + builder: (column) => ColumnOrderings(column), + ); +} + +class $$JobsTableAnnotationComposer + extends Composer<_$AppDatabase, $JobsTable> { + $$JobsTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get projectKey => $composableBuilder( + column: $table.projectKey, + builder: (column) => column, + ); + + GeneratedColumn get issueNumber => $composableBuilder( + column: $table.issueNumber, + builder: (column) => column, + ); + + GeneratedColumn get fingerprint => $composableBuilder( + column: $table.fingerprint, + builder: (column) => column, + ); + + GeneratedColumn get trigger => + $composableBuilder(column: $table.trigger, builder: (column) => column); + + GeneratedColumn get status => + $composableBuilder(column: $table.status, builder: (column) => column); + + GeneratedColumn get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => column); + + GeneratedColumn get updatedAt => + $composableBuilder(column: $table.updatedAt, builder: (column) => column); +} + +class $$JobsTableTableManager + extends + RootTableManager< + _$AppDatabase, + $JobsTable, + Job, + $$JobsTableFilterComposer, + $$JobsTableOrderingComposer, + $$JobsTableAnnotationComposer, + $$JobsTableCreateCompanionBuilder, + $$JobsTableUpdateCompanionBuilder, + (Job, BaseReferences<_$AppDatabase, $JobsTable, Job>), + Job, + PrefetchHooks Function() + > { + $$JobsTableTableManager(_$AppDatabase db, $JobsTable table) + : super( + TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$JobsTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$JobsTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$JobsTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value projectKey = const Value.absent(), + Value issueNumber = const Value.absent(), + Value fingerprint = const Value.absent(), + Value trigger = const Value.absent(), + Value status = const Value.absent(), + Value createdAt = const Value.absent(), + Value updatedAt = const Value.absent(), + }) => JobsCompanion( + id: id, + projectKey: projectKey, + issueNumber: issueNumber, + fingerprint: fingerprint, + trigger: trigger, + status: status, + createdAt: createdAt, + updatedAt: updatedAt, + ), + createCompanionCallback: + ({ + Value id = const Value.absent(), + required String projectKey, + required int issueNumber, + required String fingerprint, + required String trigger, + required String status, + required String createdAt, + required String updatedAt, + }) => JobsCompanion.insert( + id: id, + projectKey: projectKey, + issueNumber: issueNumber, + fingerprint: fingerprint, + trigger: trigger, + status: status, + createdAt: createdAt, + updatedAt: updatedAt, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + ), + ); +} + +typedef $$JobsTableProcessedTableManager = + ProcessedTableManager< + _$AppDatabase, + $JobsTable, + Job, + $$JobsTableFilterComposer, + $$JobsTableOrderingComposer, + $$JobsTableAnnotationComposer, + $$JobsTableCreateCompanionBuilder, + $$JobsTableUpdateCompanionBuilder, + (Job, BaseReferences<_$AppDatabase, $JobsTable, Job>), + Job, + PrefetchHooks Function() + >; +typedef $$ExecutionRunsTableCreateCompanionBuilder = + ExecutionRunsCompanion Function({ + Value id, + required int jobId, + required String responderId, + required String mode, + required String decision, + required bool success, + Value workspacePath, + required String stdout, + required String stderr, + required String createdAt, + }); +typedef $$ExecutionRunsTableUpdateCompanionBuilder = + ExecutionRunsCompanion Function({ + Value id, + Value jobId, + Value responderId, + Value mode, + Value decision, + Value success, + Value workspacePath, + Value stdout, + Value stderr, + Value createdAt, + }); + +class $$ExecutionRunsTableFilterComposer + extends Composer<_$AppDatabase, $ExecutionRunsTable> { + $$ExecutionRunsTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get jobId => $composableBuilder( + column: $table.jobId, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get responderId => $composableBuilder( + column: $table.responderId, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get mode => $composableBuilder( + column: $table.mode, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get decision => $composableBuilder( + column: $table.decision, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get success => $composableBuilder( + column: $table.success, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get workspacePath => $composableBuilder( + column: $table.workspacePath, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get stdout => $composableBuilder( + column: $table.stdout, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get stderr => $composableBuilder( + column: $table.stderr, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => ColumnFilters(column), + ); +} + +class $$ExecutionRunsTableOrderingComposer + extends Composer<_$AppDatabase, $ExecutionRunsTable> { + $$ExecutionRunsTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get jobId => $composableBuilder( + column: $table.jobId, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get responderId => $composableBuilder( + column: $table.responderId, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get mode => $composableBuilder( + column: $table.mode, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get decision => $composableBuilder( + column: $table.decision, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get success => $composableBuilder( + column: $table.success, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get workspacePath => $composableBuilder( + column: $table.workspacePath, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get stdout => $composableBuilder( + column: $table.stdout, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get stderr => $composableBuilder( + column: $table.stderr, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => ColumnOrderings(column), + ); +} + +class $$ExecutionRunsTableAnnotationComposer + extends Composer<_$AppDatabase, $ExecutionRunsTable> { + $$ExecutionRunsTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get jobId => + $composableBuilder(column: $table.jobId, builder: (column) => column); + + GeneratedColumn get responderId => $composableBuilder( + column: $table.responderId, + builder: (column) => column, + ); + + GeneratedColumn get mode => + $composableBuilder(column: $table.mode, builder: (column) => column); + + GeneratedColumn get decision => + $composableBuilder(column: $table.decision, builder: (column) => column); + + GeneratedColumn get success => + $composableBuilder(column: $table.success, builder: (column) => column); + + GeneratedColumn get workspacePath => $composableBuilder( + column: $table.workspacePath, + builder: (column) => column, + ); + + GeneratedColumn get stdout => + $composableBuilder(column: $table.stdout, builder: (column) => column); + + GeneratedColumn get stderr => + $composableBuilder(column: $table.stderr, builder: (column) => column); + + GeneratedColumn get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => column); +} + +class $$ExecutionRunsTableTableManager + extends + RootTableManager< + _$AppDatabase, + $ExecutionRunsTable, + ExecutionRun, + $$ExecutionRunsTableFilterComposer, + $$ExecutionRunsTableOrderingComposer, + $$ExecutionRunsTableAnnotationComposer, + $$ExecutionRunsTableCreateCompanionBuilder, + $$ExecutionRunsTableUpdateCompanionBuilder, + ( + ExecutionRun, + BaseReferences<_$AppDatabase, $ExecutionRunsTable, ExecutionRun>, + ), + ExecutionRun, + PrefetchHooks Function() + > { + $$ExecutionRunsTableTableManager(_$AppDatabase db, $ExecutionRunsTable table) + : super( + TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$ExecutionRunsTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$ExecutionRunsTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$ExecutionRunsTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value jobId = const Value.absent(), + Value responderId = const Value.absent(), + Value mode = const Value.absent(), + Value decision = const Value.absent(), + Value success = const Value.absent(), + Value workspacePath = const Value.absent(), + Value stdout = const Value.absent(), + Value stderr = const Value.absent(), + Value createdAt = const Value.absent(), + }) => ExecutionRunsCompanion( + id: id, + jobId: jobId, + responderId: responderId, + mode: mode, + decision: decision, + success: success, + workspacePath: workspacePath, + stdout: stdout, + stderr: stderr, + createdAt: createdAt, + ), + createCompanionCallback: + ({ + Value id = const Value.absent(), + required int jobId, + required String responderId, + required String mode, + required String decision, + required bool success, + Value workspacePath = const Value.absent(), + required String stdout, + required String stderr, + required String createdAt, + }) => ExecutionRunsCompanion.insert( + id: id, + jobId: jobId, + responderId: responderId, + mode: mode, + decision: decision, + success: success, + workspacePath: workspacePath, + stdout: stdout, + stderr: stderr, + createdAt: createdAt, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + ), + ); +} + +typedef $$ExecutionRunsTableProcessedTableManager = + ProcessedTableManager< + _$AppDatabase, + $ExecutionRunsTable, + ExecutionRun, + $$ExecutionRunsTableFilterComposer, + $$ExecutionRunsTableOrderingComposer, + $$ExecutionRunsTableAnnotationComposer, + $$ExecutionRunsTableCreateCompanionBuilder, + $$ExecutionRunsTableUpdateCompanionBuilder, + ( + ExecutionRun, + BaseReferences<_$AppDatabase, $ExecutionRunsTable, ExecutionRun>, + ), + ExecutionRun, + PrefetchHooks Function() + >; +typedef $$CommentsTableCreateCompanionBuilder = + CommentsCompanion Function({ + Value id, + required String projectKey, + required int issueNumber, + required String fingerprint, + required int githubCommentId, + required String githubCommentUrl, + required String body, + required String createdAt, + }); +typedef $$CommentsTableUpdateCompanionBuilder = + CommentsCompanion Function({ + Value id, + Value projectKey, + Value issueNumber, + Value fingerprint, + Value githubCommentId, + Value githubCommentUrl, + Value body, + Value createdAt, + }); + +class $$CommentsTableFilterComposer + extends Composer<_$AppDatabase, $CommentsTable> { + $$CommentsTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get projectKey => $composableBuilder( + column: $table.projectKey, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get issueNumber => $composableBuilder( + column: $table.issueNumber, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get fingerprint => $composableBuilder( + column: $table.fingerprint, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get githubCommentId => $composableBuilder( + column: $table.githubCommentId, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get githubCommentUrl => $composableBuilder( + column: $table.githubCommentUrl, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get body => $composableBuilder( + column: $table.body, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => ColumnFilters(column), + ); +} + +class $$CommentsTableOrderingComposer + extends Composer<_$AppDatabase, $CommentsTable> { + $$CommentsTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get projectKey => $composableBuilder( + column: $table.projectKey, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get issueNumber => $composableBuilder( + column: $table.issueNumber, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get fingerprint => $composableBuilder( + column: $table.fingerprint, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get githubCommentId => $composableBuilder( + column: $table.githubCommentId, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get githubCommentUrl => $composableBuilder( + column: $table.githubCommentUrl, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get body => $composableBuilder( + column: $table.body, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => ColumnOrderings(column), + ); +} + +class $$CommentsTableAnnotationComposer + extends Composer<_$AppDatabase, $CommentsTable> { + $$CommentsTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get projectKey => $composableBuilder( + column: $table.projectKey, + builder: (column) => column, + ); + + GeneratedColumn get issueNumber => $composableBuilder( + column: $table.issueNumber, + builder: (column) => column, + ); + + GeneratedColumn get fingerprint => $composableBuilder( + column: $table.fingerprint, + builder: (column) => column, + ); + + GeneratedColumn get githubCommentId => $composableBuilder( + column: $table.githubCommentId, + builder: (column) => column, + ); + + GeneratedColumn get githubCommentUrl => $composableBuilder( + column: $table.githubCommentUrl, + builder: (column) => column, + ); + + GeneratedColumn get body => + $composableBuilder(column: $table.body, builder: (column) => column); + + GeneratedColumn get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => column); +} + +class $$CommentsTableTableManager + extends + RootTableManager< + _$AppDatabase, + $CommentsTable, + Comment, + $$CommentsTableFilterComposer, + $$CommentsTableOrderingComposer, + $$CommentsTableAnnotationComposer, + $$CommentsTableCreateCompanionBuilder, + $$CommentsTableUpdateCompanionBuilder, + (Comment, BaseReferences<_$AppDatabase, $CommentsTable, Comment>), + Comment, + PrefetchHooks Function() + > { + $$CommentsTableTableManager(_$AppDatabase db, $CommentsTable table) + : super( + TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$CommentsTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$CommentsTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$CommentsTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value projectKey = const Value.absent(), + Value issueNumber = const Value.absent(), + Value fingerprint = const Value.absent(), + Value githubCommentId = const Value.absent(), + Value githubCommentUrl = const Value.absent(), + Value body = const Value.absent(), + Value createdAt = const Value.absent(), + }) => CommentsCompanion( + id: id, + projectKey: projectKey, + issueNumber: issueNumber, + fingerprint: fingerprint, + githubCommentId: githubCommentId, + githubCommentUrl: githubCommentUrl, + body: body, + createdAt: createdAt, + ), + createCompanionCallback: + ({ + Value id = const Value.absent(), + required String projectKey, + required int issueNumber, + required String fingerprint, + required int githubCommentId, + required String githubCommentUrl, + required String body, + required String createdAt, + }) => CommentsCompanion.insert( + id: id, + projectKey: projectKey, + issueNumber: issueNumber, + fingerprint: fingerprint, + githubCommentId: githubCommentId, + githubCommentUrl: githubCommentUrl, + body: body, + createdAt: createdAt, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + ), + ); +} + +typedef $$CommentsTableProcessedTableManager = + ProcessedTableManager< + _$AppDatabase, + $CommentsTable, + Comment, + $$CommentsTableFilterComposer, + $$CommentsTableOrderingComposer, + $$CommentsTableAnnotationComposer, + $$CommentsTableCreateCompanionBuilder, + $$CommentsTableUpdateCompanionBuilder, + (Comment, BaseReferences<_$AppDatabase, $CommentsTable, Comment>), + Comment, + PrefetchHooks Function() + >; +typedef $$PollRunsTableCreateCompanionBuilder = + PollRunsCompanion Function({ + Value id, + required String projectKey, + required String startedAt, + Value finishedAt, + Value success, + Value error, + }); +typedef $$PollRunsTableUpdateCompanionBuilder = + PollRunsCompanion Function({ + Value id, + Value projectKey, + Value startedAt, + Value finishedAt, + Value success, + Value error, + }); + +class $$PollRunsTableFilterComposer + extends Composer<_$AppDatabase, $PollRunsTable> { + $$PollRunsTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get projectKey => $composableBuilder( + column: $table.projectKey, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get startedAt => $composableBuilder( + column: $table.startedAt, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get finishedAt => $composableBuilder( + column: $table.finishedAt, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get success => $composableBuilder( + column: $table.success, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get error => $composableBuilder( + column: $table.error, + builder: (column) => ColumnFilters(column), + ); +} + +class $$PollRunsTableOrderingComposer + extends Composer<_$AppDatabase, $PollRunsTable> { + $$PollRunsTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get projectKey => $composableBuilder( + column: $table.projectKey, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get startedAt => $composableBuilder( + column: $table.startedAt, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get finishedAt => $composableBuilder( + column: $table.finishedAt, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get success => $composableBuilder( + column: $table.success, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get error => $composableBuilder( + column: $table.error, + builder: (column) => ColumnOrderings(column), + ); +} + +class $$PollRunsTableAnnotationComposer + extends Composer<_$AppDatabase, $PollRunsTable> { + $$PollRunsTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get projectKey => $composableBuilder( + column: $table.projectKey, + builder: (column) => column, + ); + + GeneratedColumn get startedAt => + $composableBuilder(column: $table.startedAt, builder: (column) => column); + + GeneratedColumn get finishedAt => $composableBuilder( + column: $table.finishedAt, + builder: (column) => column, + ); + + GeneratedColumn get success => + $composableBuilder(column: $table.success, builder: (column) => column); + + GeneratedColumn get error => + $composableBuilder(column: $table.error, builder: (column) => column); +} + +class $$PollRunsTableTableManager + extends + RootTableManager< + _$AppDatabase, + $PollRunsTable, + PollRun, + $$PollRunsTableFilterComposer, + $$PollRunsTableOrderingComposer, + $$PollRunsTableAnnotationComposer, + $$PollRunsTableCreateCompanionBuilder, + $$PollRunsTableUpdateCompanionBuilder, + (PollRun, BaseReferences<_$AppDatabase, $PollRunsTable, PollRun>), + PollRun, + PrefetchHooks Function() + > { + $$PollRunsTableTableManager(_$AppDatabase db, $PollRunsTable table) + : super( + TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$PollRunsTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$PollRunsTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$PollRunsTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value projectKey = const Value.absent(), + Value startedAt = const Value.absent(), + Value finishedAt = const Value.absent(), + Value success = const Value.absent(), + Value error = const Value.absent(), + }) => PollRunsCompanion( + id: id, + projectKey: projectKey, + startedAt: startedAt, + finishedAt: finishedAt, + success: success, + error: error, + ), + createCompanionCallback: + ({ + Value id = const Value.absent(), + required String projectKey, + required String startedAt, + Value finishedAt = const Value.absent(), + Value success = const Value.absent(), + Value error = const Value.absent(), + }) => PollRunsCompanion.insert( + id: id, + projectKey: projectKey, + startedAt: startedAt, + finishedAt: finishedAt, + success: success, + error: error, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + ), + ); +} + +typedef $$PollRunsTableProcessedTableManager = + ProcessedTableManager< + _$AppDatabase, + $PollRunsTable, + PollRun, + $$PollRunsTableFilterComposer, + $$PollRunsTableOrderingComposer, + $$PollRunsTableAnnotationComposer, + $$PollRunsTableCreateCompanionBuilder, + $$PollRunsTableUpdateCompanionBuilder, + (PollRun, BaseReferences<_$AppDatabase, $PollRunsTable, PollRun>), + PollRun, + PrefetchHooks Function() + >; + +class $AppDatabaseManager { + final _$AppDatabase _db; + $AppDatabaseManager(this._db); + $$ProjectStatesTableTableManager get projectStates => + $$ProjectStatesTableTableManager(_db, _db.projectStates); + $$IssueThreadStatesTableTableManager get issueThreadStates => + $$IssueThreadStatesTableTableManager(_db, _db.issueThreadStates); + $$JobsTableTableManager get jobs => $$JobsTableTableManager(_db, _db.jobs); + $$ExecutionRunsTableTableManager get executionRuns => + $$ExecutionRunsTableTableManager(_db, _db.executionRuns); + $$CommentsTableTableManager get comments => + $$CommentsTableTableManager(_db, _db.comments); + $$PollRunsTableTableManager get pollRuns => + $$PollRunsTableTableManager(_db, _db.pollRuns); +} diff --git a/lib/src/github_client.dart b/lib/src/github_client.dart new file mode 100644 index 0000000..393b50d --- /dev/null +++ b/lib/src/github_client.dart @@ -0,0 +1,227 @@ +import 'dart:convert'; +import 'dart:io'; + +class GitHubClient { + GitHubClient({this.ghCommand = 'gh'}); + + final String ghCommand; + + Future> fetchUpdatedIssues({ + required String repo, + required DateTime? since, + }) async { + final query = { + 'state': 'all', + 'sort': 'updated', + 'direction': 'asc', + 'per_page': '100', + if (since != null) 'since': since.toUtc().toIso8601String(), + }; + + final json = await _runJson([ + 'api', + '-X', + 'GET', + 'repos/$repo/issues${_encodeQuery(query)}', + ]); + + return (json as List) + .cast>() + .where((item) => !item.containsKey('pull_request')) + .map(GitHubIssueSummary.fromJson) + .toList(growable: false); + } + + Future fetchThread({ + required String repo, + required GitHubIssueSummary issue, + }) async { + final comments = await _runJson([ + 'api', + '-X', + 'GET', + 'repos/$repo/issues/${issue.number}/comments?per_page=100', + ]); + + return IssueThread( + issue: issue, + comments: (comments as List) + .cast>() + .map(GitHubIssueComment.fromJson) + .toList(growable: false), + ); + } + + Future createIssueComment({ + required String repo, + required int issueNumber, + required String body, + }) async { + final json = await _runJson([ + 'api', + '-X', + 'POST', + 'repos/$repo/issues/$issueNumber/comments', + '-f', + 'body=$body', + ]); + + final map = json as Map; + return PostedComment( + id: map['id'] as int, + url: map['html_url'] as String? ?? '', + body: map['body'] as String? ?? body, + ); + } + + Future _runJson(List arguments) async { + final result = await Process.run(ghCommand, arguments); + if (result.exitCode != 0) { + throw ProcessException( + ghCommand, + arguments, + '${result.stdout}\n${result.stderr}', + result.exitCode, + ); + } + + final stdoutText = result.stdout.toString().trim(); + if (stdoutText.isEmpty) { + return null; + } + return jsonDecode(stdoutText); + } + + String _encodeQuery(Map query) { + if (query.isEmpty) { + return ''; + } + + final encoded = query.entries + .map( + (entry) => + '${Uri.encodeQueryComponent(entry.key)}=' + '${Uri.encodeQueryComponent(entry.value)}', + ) + .join('&'); + return '?$encoded'; + } +} + +class GitHubIssueSummary { + GitHubIssueSummary({ + required this.number, + required this.title, + required this.body, + required this.state, + required this.url, + required this.updatedAt, + required this.userLogin, + required this.labels, + }); + + final int number; + final String title; + final String body; + final String state; + final String url; + final DateTime updatedAt; + final String userLogin; + final List labels; + + factory GitHubIssueSummary.fromJson(Map json) { + final labelsNode = json['labels'] as List? ?? const []; + return GitHubIssueSummary( + number: json['number'] as int, + title: json['title'] as String? ?? '', + body: json['body'] as String? ?? '', + state: json['state'] as String? ?? 'open', + url: json['html_url'] as String? ?? '', + updatedAt: DateTime.parse(json['updated_at'] as String), + userLogin: + (json['user'] as Map? ?? const {})['login'] + as String? ?? + 'unknown', + labels: labelsNode + .map((label) => (label as Map)['name'].toString()) + .toList(growable: false), + ); + } + + Map toJson() => { + 'number': number, + 'title': title, + 'body': body, + 'state': state, + 'url': url, + 'updated_at': updatedAt.toUtc().toIso8601String(), + 'user_login': userLogin, + 'labels': labels, + }; +} + +class GitHubIssueComment { + GitHubIssueComment({ + required this.id, + required this.body, + required this.userLogin, + required this.createdAt, + required this.updatedAt, + required this.url, + }); + + final int id; + final String body; + final String userLogin; + final DateTime createdAt; + final DateTime updatedAt; + final String url; + + factory GitHubIssueComment.fromJson(Map json) { + return GitHubIssueComment( + id: json['id'] as int, + body: json['body'] as String? ?? '', + userLogin: + (json['user'] as Map? ?? const {})['login'] + as String? ?? + 'unknown', + createdAt: DateTime.parse(json['created_at'] as String), + updatedAt: DateTime.parse(json['updated_at'] as String), + url: json['html_url'] as String? ?? '', + ); + } + + Map toJson() => { + 'id': id, + 'body': body, + 'user_login': userLogin, + 'created_at': createdAt.toUtc().toIso8601String(), + 'updated_at': updatedAt.toUtc().toIso8601String(), + 'url': url, + }; +} + +class IssueThread { + IssueThread({required this.issue, required this.comments}); + + final GitHubIssueSummary issue; + final List comments; + + bool get isOpen => issue.state == 'open'; + + GitHubIssueComment? get latestComment => + comments.isEmpty ? null : comments.last; + + Map toJson() => { + 'issue': issue.toJson(), + 'comments': comments.map((comment) => comment.toJson()).toList(), + }; +} + +class PostedComment { + PostedComment({required this.id, required this.url, required this.body}); + + final int id; + final String url; + final String body; +} diff --git a/lib/src/issue_assistant_app.dart b/lib/src/issue_assistant_app.dart new file mode 100644 index 0000000..0049f42 --- /dev/null +++ b/lib/src/issue_assistant_app.dart @@ -0,0 +1,323 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:crypto/crypto.dart'; + +import 'cli_responder.dart'; +import 'config.dart'; +import 'database.dart'; +import 'github_client.dart'; +import 'workspace_manager.dart'; + +class IssueAssistantApp { + IssueAssistantApp._({ + required this.config, + required this.database, + required this.githubClient, + required this.responderRunner, + required this.workspaceManager, + }); + + final AppConfig config; + final AppDatabase database; + final GitHubClient githubClient; + final CliResponderRunner responderRunner; + final WorkspaceManager workspaceManager; + + static Future open({ + required AppConfig config, + required String databasePath, + String ghCommand = 'gh', + }) async { + final database = AppDatabase.open(databasePath); + return IssueAssistantApp._( + config: config, + database: database, + githubClient: GitHubClient(ghCommand: ghCommand), + responderRunner: CliResponderRunner(), + workspaceManager: WorkspaceManager(), + ); + } + + Future run() async { + while (true) { + await runOnce(); + await Future.delayed(_smallestPollInterval()); + } + } + + Future runOnce() async { + for (final project in config.projects.values) { + if (!project.issueAssistant.enabled) { + continue; + } + await _pollProject(project); + } + } + + Future close() => database.close(); + + Duration _smallestPollInterval() { + return config.projects.values + .where((project) => project.issueAssistant.enabled) + .map((project) => project.issueAssistant.pollInterval) + .fold( + const Duration(minutes: 5), + (current, next) => next < current ? next : current, + ); + } + + Future _pollProject(ProjectConfig project) async { + final pollRunId = await database.startPollRun(project.key); + try { + final projectState = await database.findProjectState(project.key); + final since = projectState?.lastSeenUpdatedAt == null + ? null + : DateTime.parse(projectState!.lastSeenUpdatedAt!); + final issues = await githubClient.fetchUpdatedIssues( + repo: project.repo, + since: since, + ); + + DateTime? latestSeen = since; + for (final issue in issues) { + if (latestSeen == null || issue.updatedAt.isAfter(latestSeen)) { + latestSeen = issue.updatedAt; + } + await _processIssue(project, issue); + } + + await database.upsertProjectState( + projectKey: project.key, + repo: project.repo, + lastSeenUpdatedAt: latestSeen, + ); + await database.finishPollRun(pollRunId: pollRunId, success: true); + } catch (error) { + await database.finishPollRun( + pollRunId: pollRunId, + success: false, + error: error.toString(), + ); + rethrow; + } + } + + Future _processIssue( + ProjectConfig project, + GitHubIssueSummary issue, + ) async { + final thread = await githubClient.fetchThread( + repo: project.repo, + issue: issue, + ); + final fingerprint = _fingerprintThread(thread); + final record = await database.findIssueThread(project.key, issue.number); + if (record?.lastEvaluatedFingerprint == fingerprint) { + return; + } + + final trigger = _determineTrigger(project, thread); + final jobId = await database.createJob( + projectKey: project.key, + issueNumber: issue.number, + fingerprint: fingerprint, + trigger: trigger, + status: 'queued', + ); + + await database.upsertIssueThread( + projectKey: project.key, + issueNumber: issue.number, + fingerprint: fingerprint, + updatedAt: issue.updatedAt, + lastEvaluatedFingerprint: record?.lastEvaluatedFingerprint, + lastDecision: record?.lastDecision, + lastCommentId: record?.lastCommentId, + lastCommentUrl: record?.lastCommentUrl, + ); + + await database.updateJobStatus(jobId, 'running'); + final forceReply = trigger == 'mention'; + final prompt = _buildPrompt( + project: project, + thread: thread, + trigger: trigger, + ); + + ResponderResult? selectedResult; + String? workspacePath; + Object? lastError; + + for (final responder in project.issueAssistant.responders) { + final needsWorktree = _looksLikeBugVerification(thread); + workspacePath = needsWorktree + ? await workspaceManager.createEphemeralWorktree(project.path) + : project.path; + + try { + final result = await responderRunner.run( + responder: responder, + project: project, + thread: thread, + prompt: prompt, + forceReply: forceReply, + workspacePath: workspacePath, + ); + await database.addExecutionRun( + jobId: jobId, + responderId: result.responderId, + mode: result.mode, + decision: result.decision, + success: true, + workspacePath: workspacePath, + stdout: result.rawStdout, + stderr: result.rawStderr, + ); + selectedResult = result; + break; + } catch (error) { + lastError = error; + await database.addExecutionRun( + jobId: jobId, + responderId: responder.id, + mode: 'error', + decision: 'no_reply', + success: false, + workspacePath: workspacePath, + stdout: '', + stderr: error.toString(), + ); + } finally { + if (workspacePath != project.path) { + await workspaceManager.disposeEphemeralWorktree(workspacePath); + } + } + } + + if (selectedResult == null) { + await database.updateJobStatus(jobId, 'failed'); + throw StateError( + 'All responders failed for ${project.key}#${issue.number}: $lastError', + ); + } + + if (!selectedResult.shouldReply) { + await database.upsertIssueThread( + projectKey: project.key, + issueNumber: issue.number, + fingerprint: fingerprint, + updatedAt: issue.updatedAt, + lastEvaluatedFingerprint: fingerprint, + lastDecision: selectedResult.decision, + lastCommentId: record?.lastCommentId, + lastCommentUrl: record?.lastCommentUrl, + ); + await database.updateJobStatus(jobId, 'completed'); + return; + } + + final commentBody = _decorateComment( + tag: project.issueAssistant.commentTag, + fingerprint: fingerprint, + markdown: selectedResult.markdown, + ); + final posted = await githubClient.createIssueComment( + repo: project.repo, + issueNumber: issue.number, + body: commentBody, + ); + + await database.addCommentRecord( + projectKey: project.key, + issueNumber: issue.number, + fingerprint: fingerprint, + githubCommentId: posted.id, + githubCommentUrl: posted.url, + body: posted.body, + ); + await database.upsertIssueThread( + projectKey: project.key, + issueNumber: issue.number, + fingerprint: fingerprint, + updatedAt: issue.updatedAt, + lastEvaluatedFingerprint: fingerprint, + lastDecision: selectedResult.decision, + lastCommentId: posted.id, + lastCommentUrl: posted.url, + ); + await database.updateJobStatus(jobId, 'completed'); + } + + String _buildPrompt({ + required ProjectConfig project, + required IssueThread thread, + required String trigger, + }) { + final payload = { + 'repo': project.repo, + 'project_key': project.key, + 'project_path': project.path, + 'issue_number': thread.issue.number.toString(), + 'issue_title': thread.issue.title, + 'issue_body': thread.issue.body, + 'issue_url': thread.issue.url, + 'thread_json': const JsonEncoder.withIndent( + ' ', + ).convert(thread.toJson()), + 'mention_triggers': project.issueAssistant.mentionTriggers.join(', '), + 'trigger': trigger, + 'capabilities': project.issueAssistant.capabilities + .map((capability) => capability.name) + .join(', '), + }; + return renderTemplate(project.issueAssistant.prompt, payload); + } + + String _determineTrigger(ProjectConfig project, IssueThread thread) { + final latestText = thread.latestComment?.body ?? thread.issue.body; + final latestAuthor = + thread.latestComment?.userLogin ?? thread.issue.userLogin; + final latestTextLower = latestText.toLowerCase(); + final mention = project.issueAssistant.mentionTriggers.any( + (trigger) => latestTextLower.contains(trigger.toLowerCase()), + ); + if (mention && !_looksLikeBot(project, latestAuthor)) { + return 'mention'; + } + return 'update'; + } + + bool _looksLikeBot(ProjectConfig project, String login) { + final lower = login.toLowerCase(); + if (lower.endsWith('[bot]')) { + return true; + } + return lower.contains(project.issueAssistant.commentTag.toLowerCase()); + } + + bool _looksLikeBugVerification(IssueThread thread) { + final text = [ + thread.issue.title, + thread.issue.body, + if (thread.latestComment != null) thread.latestComment!.body, + ].join('\n').toLowerCase(); + return text.contains('bug') && + (text.contains('verify') || + text.contains('test') || + text.contains('reproduce')); + } + + String _fingerprintThread(IssueThread thread) { + final bytes = utf8.encode(jsonEncode(thread.toJson())); + return sha256.convert(bytes).toString(); + } + + String _decorateComment({ + required String tag, + required String fingerprint, + required String markdown, + }) { + return '$markdown\n\n'; + } +} diff --git a/lib/src/workspace_manager.dart b/lib/src/workspace_manager.dart new file mode 100644 index 0000000..e155eae --- /dev/null +++ b/lib/src/workspace_manager.dart @@ -0,0 +1,62 @@ +import 'dart:io'; + +import 'package:path/path.dart' as p; + +class WorkspaceManager { + Future createEphemeralWorktree(String projectPath) async { + final parent = await Directory.systemTemp.createTemp('cws-worktree-'); + final workspacePath = p.join(parent.path, 'repo'); + final result = await Process.run('git', [ + '-C', + projectPath, + 'worktree', + 'add', + '--detach', + workspacePath, + 'HEAD', + ]); + + if (result.exitCode != 0) { + throw ProcessException( + 'git', + [ + '-C', + projectPath, + 'worktree', + 'add', + '--detach', + workspacePath, + 'HEAD', + ], + '${result.stdout}\n${result.stderr}', + result.exitCode, + ); + } + + return workspacePath; + } + + Future disposeEphemeralWorktree(String workspacePath) async { + final result = await Process.run('git', [ + '-C', + workspacePath, + 'worktree', + 'remove', + '--force', + workspacePath, + ]); + + if (result.exitCode != 0) { + final directory = Directory(p.dirname(workspacePath)); + if (await directory.exists()) { + await directory.delete(recursive: true); + } + return; + } + + final tempRoot = Directory(p.dirname(workspacePath)); + if (await tempRoot.exists()) { + await tempRoot.delete(recursive: true); + } + } +} diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..c4845b8 --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,22 @@ +name: code_work_spawner +description: GitHub issue thread assistant with Agent Orchestrator style config. +version: 1.0.0 +# repository: https://github.com/my_org/my_repo + +environment: + sdk: ^3.11.4 + +dependencies: + args: ^2.7.0 + crypto: ^3.0.6 + drift: ^2.28.2 + file: ^7.0.1 + path: ^1.9.0 + sqlite3: ^2.9.3 + yaml: ^3.1.3 + +dev_dependencies: + build_runner: ^2.6.0 + drift_dev: ^2.28.1 + lints: ^6.0.0 + test: ^1.25.6 diff --git a/test/code_work_spawner_test.dart b/test/code_work_spawner_test.dart new file mode 100644 index 0000000..1ab687e --- /dev/null +++ b/test/code_work_spawner_test.dart @@ -0,0 +1,304 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:code_work_spawner/code_work_spawner.dart'; +import 'package:drift/drift.dart' show driftRuntimeOptions; +import 'package:path/path.dart' as p; +import 'package:test/test.dart'; + +void main() { + driftRuntimeOptions.dontWarnAboutMultipleDatabases = true; + + group('AppConfig.load', () { + test( + 'given AO style config when loading then defaults and project overrides are merged', + () async { + // Given a config file with shared defaults and a project override. + final tempDir = await Directory.systemTemp.createTemp('cws-config-'); + final configFile = File( + p.join(tempDir.path, 'agent-orchestrator.yaml'), + ); + await configFile.writeAsString(''' +defaults: + issueAssistant: + enabled: true + pollInterval: 5m + mentionTriggers: ["@helper"] + prompt: "default {{repo}}" + responders: + - id: fallback + command: responder +projects: + sample: + repo: owner/sample + path: ${tempDir.path} + defaultBranch: main + issueAssistant: + prompt: "project {{project_key}}" +'''); + + // When the app config is loaded from disk. + final config = await AppConfig.load(configFile.path); + + // Then the project inherits defaults and overrides only the prompt. + expect(config.projects['sample'], isNotNull); + expect(config.projects['sample']!.issueAssistant.mentionTriggers, [ + '@helper', + ]); + expect( + config.projects['sample']!.issueAssistant.prompt, + 'project {{project_key}}', + ); + expect( + config.projects['sample']!.issueAssistant.responders.single.id, + 'fallback', + ); + }, + ); + }); + + group('IssueAssistantApp.runOnce', () { + test( + 'given an explicit mention when polling then the app posts the responder reply once', + () async { + // Given a fake gh client and a fake responder that emits a planning reply. + final sandbox = await Directory.systemTemp.createTemp('cws-run-'); + final repoDir = await Directory(p.join(sandbox.path, 'repo')).create(); + await _initGitRepo(repoDir); + + final ghScript = File(p.join(sandbox.path, 'gh')); + final ghLog = File(p.join(sandbox.path, 'gh-log.jsonl')); + final issueData = [ + { + 'number': 12, + 'title': 'Need architecture help', + 'body': 'Please review the API layering.', + 'state': 'open', + 'html_url': 'https://example.test/issues/12', + 'updated_at': '2026-04-04T12:00:00Z', + 'user': {'login': 'reporter'}, + 'labels': [ + {'name': 'help'}, + ], + }, + ]; + final commentsData = [ + { + 'id': 50, + 'body': '@helper can you plan the architecture?', + 'html_url': 'https://example.test/issues/12#issuecomment-50', + 'created_at': '2026-04-04T12:00:00Z', + 'updated_at': '2026-04-04T12:00:00Z', + 'user': {'login': 'reporter'}, + }, + ]; + final replyJson = jsonEncode({ + 'decision': 'reply', + 'mode': 'planning', + 'markdown': 'Plan:\n- separate service and persistence', + 'summary': 'posted planning advice', + }); + await ghScript.writeAsString('''#!/usr/bin/env bash +set -euo pipefail +printf '%s\n' "\$*" >> "${ghLog.path}" +if [[ "\$1" == "api" && "\$4" == repos/owner/sample/issues\\?* ]]; then + cat <<'JSON' +${jsonEncode(issueData)} +JSON + exit 0 +fi +if [[ "\$1" == "api" && "\$4" == "repos/owner/sample/issues/12/comments?per_page=100" ]]; then + cat <<'JSON' +${jsonEncode(commentsData)} +JSON + exit 0 +fi +if [[ "\$1" == "api" && "\$4" == "repos/owner/sample/issues/12/comments" ]]; then + cat <<'JSON' +{"id": 700, "html_url": "https://example.test/comment/700", "body": "posted"} +JSON + exit 0 +fi +echo "unexpected gh args: \$*" >&2 +exit 1 +'''); + await Process.run('chmod', ['+x', ghScript.path]); + + final responderScript = File(p.join(sandbox.path, 'responder.sh')); + await responderScript.writeAsString('''#!/usr/bin/env bash +set -euo pipefail +cat >/dev/null +cat <<'JSON' +$replyJson +JSON +'''); + await Process.run('chmod', ['+x', responderScript.path]); + + final configFile = File( + p.join(sandbox.path, 'agent-orchestrator.yaml'), + ); + await configFile.writeAsString(''' +defaults: + issueAssistant: + enabled: true + pollInterval: 5m + mentionTriggers: ["@helper"] + responders: + - id: primary + command: ${responderScript.path} + timeout: 1m +projects: + sample: + repo: owner/sample + path: ${repoDir.path} + defaultBranch: main +'''); + + final config = await AppConfig.load(configFile.path); + final app = await IssueAssistantApp.open( + config: config, + databasePath: p.join(sandbox.path, 'state.sqlite3'), + ghCommand: ghScript.path, + ); + + // When the app processes a single polling cycle. + await app.runOnce(); + await app.close(); + + // Then a GitHub comment is posted for the mentioned issue. + final logLines = await ghLog.readAsLines(); + expect( + logLines + .where( + (line) => + line.contains('repos/owner/sample/issues/12/comments'), + ) + .length, + 2, + ); + }, + ); + + test( + 'given a thread update without a useful delta when polling then the app records no reply', + () async { + // Given a fake gh client and a responder that explicitly returns no_reply. + final sandbox = await Directory.systemTemp.createTemp('cws-no-reply-'); + final repoDir = await Directory(p.join(sandbox.path, 'repo')).create(); + await _initGitRepo(repoDir); + + final ghScript = File(p.join(sandbox.path, 'gh')); + final issueData = [ + { + 'number': 42, + 'title': 'Existing discussion', + 'body': 'Initial context', + 'state': 'open', + 'html_url': 'https://example.test/issues/42', + 'updated_at': '2026-04-04T13:00:00Z', + 'user': {'login': 'reporter'}, + 'labels': const [], + }, + ]; + final commentsData = [ + { + 'id': 99, + 'body': 'I think this is already covered.', + 'html_url': 'https://example.test/issues/42#issuecomment-99', + 'created_at': '2026-04-04T13:00:00Z', + 'updated_at': '2026-04-04T13:00:00Z', + 'user': {'login': 'reporter'}, + }, + ]; + final noReplyJson = jsonEncode({ + 'decision': 'no_reply', + 'mode': 'planning', + 'summary': 'thread already resolved', + }); + await ghScript.writeAsString('''#!/usr/bin/env bash +set -euo pipefail +if [[ "\$1" == "api" && "\$4" == repos/owner/sample/issues\\?* ]]; then + cat <<'JSON' +${jsonEncode(issueData)} +JSON + exit 0 +fi +if [[ "\$1" == "api" && "\$4" == "repos/owner/sample/issues/42/comments?per_page=100" ]]; then + cat <<'JSON' +${jsonEncode(commentsData)} +JSON + exit 0 +fi +echo "unexpected gh args: \$*" >&2 +exit 1 +'''); + await Process.run('chmod', ['+x', ghScript.path]); + + final responderScript = File(p.join(sandbox.path, 'responder.sh')); + await responderScript.writeAsString('''#!/usr/bin/env bash +set -euo pipefail +cat >/dev/null +cat <<'JSON' +$noReplyJson +JSON +'''); + await Process.run('chmod', ['+x', responderScript.path]); + + final configFile = File( + p.join(sandbox.path, 'agent-orchestrator.yaml'), + ); + await configFile.writeAsString(''' +defaults: + issueAssistant: + enabled: true + pollInterval: 5m + mentionTriggers: ["@helper"] + responders: + - id: primary + command: ${responderScript.path} +projects: + sample: + repo: owner/sample + path: ${repoDir.path} +'''); + + final app = await IssueAssistantApp.open( + config: await AppConfig.load(configFile.path), + databasePath: p.join(sandbox.path, 'state.sqlite3'), + ghCommand: ghScript.path, + ); + + // When a poll cycle evaluates the unchanged discussion. + await app.runOnce(); + final thread = await app.database.findIssueThread('sample', 42); + await app.close(); + + // Then the issue is marked as evaluated without storing a comment. + expect(thread, isNotNull); + expect(thread!.lastDecision, 'no_reply'); + expect(thread.lastCommentId, isNull); + }, + ); + }); +} + +Future _initGitRepo(Directory directory) async { + await Process.run('git', ['init'], workingDirectory: directory.path); + await Process.run('git', [ + 'config', + 'user.email', + 'test@example.com', + ], workingDirectory: directory.path); + await Process.run('git', [ + 'config', + 'user.name', + 'Test User', + ], workingDirectory: directory.path); + await File(p.join(directory.path, 'README.md')).writeAsString('repo'); + await Process.run('git', ['add', '.'], workingDirectory: directory.path); + await Process.run('git', [ + 'commit', + '-m', + 'init', + ], workingDirectory: directory.path); +}