@ttfw/envoi 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +238 -0
- package/dist/commands/app.d.ts +2 -0
- package/dist/commands/app.d.ts.map +1 -0
- package/dist/commands/app.js +31 -0
- package/dist/commands/app.js.map +1 -0
- package/dist/commands/autonomy.d.ts +6 -0
- package/dist/commands/autonomy.d.ts.map +1 -0
- package/dist/commands/autonomy.js +89 -0
- package/dist/commands/autonomy.js.map +1 -0
- package/dist/commands/builder.d.ts +13 -0
- package/dist/commands/builder.d.ts.map +1 -0
- package/dist/commands/builder.js +142 -0
- package/dist/commands/builder.js.map +1 -0
- package/dist/commands/idea.d.ts +12 -0
- package/dist/commands/idea.d.ts.map +1 -0
- package/dist/commands/idea.js +79 -0
- package/dist/commands/idea.js.map +1 -0
- package/dist/commands/init.d.ts +18 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +423 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/mode.d.ts +13 -0
- package/dist/commands/mode.d.ts.map +1 -0
- package/dist/commands/mode.js +96 -0
- package/dist/commands/mode.js.map +1 -0
- package/dist/commands/onboard.d.ts +37 -0
- package/dist/commands/onboard.d.ts.map +1 -0
- package/dist/commands/onboard.js +743 -0
- package/dist/commands/onboard.js.map +1 -0
- package/dist/commands/pr-note.d.ts +8 -0
- package/dist/commands/pr-note.d.ts.map +1 -0
- package/dist/commands/pr-note.js +27 -0
- package/dist/commands/pr-note.js.map +1 -0
- package/dist/commands/undo.d.ts +7 -0
- package/dist/commands/undo.d.ts.map +1 -0
- package/dist/commands/undo.js +59 -0
- package/dist/commands/undo.js.map +1 -0
- package/dist/commands/update.d.ts +24 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +248 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/constants/report_codes.d.ts +29 -0
- package/dist/constants/report_codes.d.ts.map +1 -0
- package/dist/constants/report_codes.js +69 -0
- package/dist/constants/report_codes.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +675 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/autonomy.d.ts +16 -0
- package/dist/lib/autonomy.d.ts.map +1 -0
- package/dist/lib/autonomy.js +38 -0
- package/dist/lib/autonomy.js.map +1 -0
- package/dist/lib/blocked.d.ts +87 -0
- package/dist/lib/blocked.d.ts.map +1 -0
- package/dist/lib/blocked.js +134 -0
- package/dist/lib/blocked.js.map +1 -0
- package/dist/lib/branding.d.ts +13 -0
- package/dist/lib/branding.d.ts.map +1 -0
- package/dist/lib/branding.js +19 -0
- package/dist/lib/branding.js.map +1 -0
- package/dist/lib/claude.d.ts +42 -0
- package/dist/lib/claude.d.ts.map +1 -0
- package/dist/lib/claude.js +291 -0
- package/dist/lib/claude.js.map +1 -0
- package/dist/lib/config.d.ts +71 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +410 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/diff.d.ts +150 -0
- package/dist/lib/diff.d.ts.map +1 -0
- package/dist/lib/diff.js +257 -0
- package/dist/lib/diff.js.map +1 -0
- package/dist/lib/doctor.d.ts +67 -0
- package/dist/lib/doctor.d.ts.map +1 -0
- package/dist/lib/doctor.js +211 -0
- package/dist/lib/doctor.js.map +1 -0
- package/dist/lib/fingerprint.d.ts +27 -0
- package/dist/lib/fingerprint.d.ts.map +1 -0
- package/dist/lib/fingerprint.js +116 -0
- package/dist/lib/fingerprint.js.map +1 -0
- package/dist/lib/fs.d.ts +93 -0
- package/dist/lib/fs.d.ts.map +1 -0
- package/dist/lib/fs.js +179 -0
- package/dist/lib/fs.js.map +1 -0
- package/dist/lib/git.d.ts +177 -0
- package/dist/lib/git.d.ts.map +1 -0
- package/dist/lib/git.js +355 -0
- package/dist/lib/git.js.map +1 -0
- package/dist/lib/git_branching.d.ts +84 -0
- package/dist/lib/git_branching.d.ts.map +1 -0
- package/dist/lib/git_branching.js +327 -0
- package/dist/lib/git_branching.js.map +1 -0
- package/dist/lib/gitignore.d.ts +26 -0
- package/dist/lib/gitignore.d.ts.map +1 -0
- package/dist/lib/gitignore.js +119 -0
- package/dist/lib/gitignore.js.map +1 -0
- package/dist/lib/guardrails.d.ts +232 -0
- package/dist/lib/guardrails.d.ts.map +1 -0
- package/dist/lib/guardrails.js +323 -0
- package/dist/lib/guardrails.js.map +1 -0
- package/dist/lib/history.d.ts +110 -0
- package/dist/lib/history.d.ts.map +1 -0
- package/dist/lib/history.js +236 -0
- package/dist/lib/history.js.map +1 -0
- package/dist/lib/index.d.ts +29 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/index.js +29 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/json-extract.d.ts +42 -0
- package/dist/lib/json-extract.d.ts.map +1 -0
- package/dist/lib/json-extract.js +201 -0
- package/dist/lib/json-extract.js.map +1 -0
- package/dist/lib/judge.d.ts +237 -0
- package/dist/lib/judge.d.ts.map +1 -0
- package/dist/lib/judge.js +501 -0
- package/dist/lib/judge.js.map +1 -0
- package/dist/lib/lock.d.ts +79 -0
- package/dist/lib/lock.d.ts.map +1 -0
- package/dist/lib/lock.js +254 -0
- package/dist/lib/lock.js.map +1 -0
- package/dist/lib/migration.d.ts +9 -0
- package/dist/lib/migration.d.ts.map +1 -0
- package/dist/lib/migration.js +74 -0
- package/dist/lib/migration.js.map +1 -0
- package/dist/lib/paths.d.ts +18 -0
- package/dist/lib/paths.d.ts.map +1 -0
- package/dist/lib/paths.js +27 -0
- package/dist/lib/paths.js.map +1 -0
- package/dist/lib/preflight.d.ts +33 -0
- package/dist/lib/preflight.d.ts.map +1 -0
- package/dist/lib/preflight.js +177 -0
- package/dist/lib/preflight.js.map +1 -0
- package/dist/lib/prompt_budget.d.ts +18 -0
- package/dist/lib/prompt_budget.d.ts.map +1 -0
- package/dist/lib/prompt_budget.js +36 -0
- package/dist/lib/prompt_budget.js.map +1 -0
- package/dist/lib/report.d.ts +102 -0
- package/dist/lib/report.d.ts.map +1 -0
- package/dist/lib/report.js +347 -0
- package/dist/lib/report.js.map +1 -0
- package/dist/lib/reviewer-flow.d.ts +80 -0
- package/dist/lib/reviewer-flow.d.ts.map +1 -0
- package/dist/lib/reviewer-flow.js +138 -0
- package/dist/lib/reviewer-flow.js.map +1 -0
- package/dist/lib/reviewer.d.ts +53 -0
- package/dist/lib/reviewer.d.ts.map +1 -0
- package/dist/lib/reviewer.js +199 -0
- package/dist/lib/reviewer.js.map +1 -0
- package/dist/lib/risk.d.ts +127 -0
- package/dist/lib/risk.d.ts.map +1 -0
- package/dist/lib/risk.js +192 -0
- package/dist/lib/risk.js.map +1 -0
- package/dist/lib/rollback.d.ts +143 -0
- package/dist/lib/rollback.d.ts.map +1 -0
- package/dist/lib/rollback.js +244 -0
- package/dist/lib/rollback.js.map +1 -0
- package/dist/lib/schema.d.ts +47 -0
- package/dist/lib/schema.d.ts.map +1 -0
- package/dist/lib/schema.js +91 -0
- package/dist/lib/schema.js.map +1 -0
- package/dist/lib/scope.d.ts +89 -0
- package/dist/lib/scope.d.ts.map +1 -0
- package/dist/lib/scope.js +135 -0
- package/dist/lib/scope.js.map +1 -0
- package/dist/lib/self_update.d.ts +13 -0
- package/dist/lib/self_update.d.ts.map +1 -0
- package/dist/lib/self_update.js +172 -0
- package/dist/lib/self_update.js.map +1 -0
- package/dist/lib/state.d.ts +143 -0
- package/dist/lib/state.d.ts.map +1 -0
- package/dist/lib/state.js +258 -0
- package/dist/lib/state.js.map +1 -0
- package/dist/lib/tick.d.ts +310 -0
- package/dist/lib/tick.d.ts.map +1 -0
- package/dist/lib/tick.js +424 -0
- package/dist/lib/tick.js.map +1 -0
- package/dist/lib/transport.d.ts +145 -0
- package/dist/lib/transport.d.ts.map +1 -0
- package/dist/lib/transport.js +237 -0
- package/dist/lib/transport.js.map +1 -0
- package/dist/lib/verdict_labels.d.ts +5 -0
- package/dist/lib/verdict_labels.d.ts.map +1 -0
- package/dist/lib/verdict_labels.js +25 -0
- package/dist/lib/verdict_labels.js.map +1 -0
- package/dist/lib/verify-safety.d.ts +63 -0
- package/dist/lib/verify-safety.d.ts.map +1 -0
- package/dist/lib/verify-safety.js +123 -0
- package/dist/lib/verify-safety.js.map +1 -0
- package/dist/lib/verify.d.ts +139 -0
- package/dist/lib/verify.d.ts.map +1 -0
- package/dist/lib/verify.js +311 -0
- package/dist/lib/verify.js.map +1 -0
- package/dist/lib/workspace_state.d.ts +79 -0
- package/dist/lib/workspace_state.d.ts.map +1 -0
- package/dist/lib/workspace_state.js +283 -0
- package/dist/lib/workspace_state.js.map +1 -0
- package/dist/runner/builder.d.ts +58 -0
- package/dist/runner/builder.d.ts.map +1 -0
- package/dist/runner/builder.js +775 -0
- package/dist/runner/builder.js.map +1 -0
- package/dist/runner/builder_parse.d.ts +37 -0
- package/dist/runner/builder_parse.d.ts.map +1 -0
- package/dist/runner/builder_parse.js +76 -0
- package/dist/runner/builder_parse.js.map +1 -0
- package/dist/runner/index.d.ts +9 -0
- package/dist/runner/index.d.ts.map +1 -0
- package/dist/runner/index.js +7 -0
- package/dist/runner/index.js.map +1 -0
- package/dist/runner/loop.d.ts +51 -0
- package/dist/runner/loop.d.ts.map +1 -0
- package/dist/runner/loop.js +221 -0
- package/dist/runner/loop.js.map +1 -0
- package/dist/runner/orchestrator.d.ts +67 -0
- package/dist/runner/orchestrator.d.ts.map +1 -0
- package/dist/runner/orchestrator.js +376 -0
- package/dist/runner/orchestrator.js.map +1 -0
- package/dist/runner/tick.d.ts +10 -0
- package/dist/runner/tick.d.ts.map +1 -0
- package/dist/runner/tick.js +1639 -0
- package/dist/runner/tick.js.map +1 -0
- package/dist/types/blocked.d.ts +52 -0
- package/dist/types/blocked.d.ts.map +1 -0
- package/dist/types/blocked.js +8 -0
- package/dist/types/blocked.js.map +1 -0
- package/dist/types/builder.d.ts +25 -0
- package/dist/types/builder.d.ts.map +1 -0
- package/dist/types/builder.js +7 -0
- package/dist/types/builder.js.map +1 -0
- package/dist/types/claude.d.ts +86 -0
- package/dist/types/claude.d.ts.map +1 -0
- package/dist/types/claude.js +48 -0
- package/dist/types/claude.js.map +1 -0
- package/dist/types/config.d.ts +384 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +7 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/index.d.ts +18 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +8 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/lock.d.ts +21 -0
- package/dist/types/lock.d.ts.map +1 -0
- package/dist/types/lock.js +8 -0
- package/dist/types/lock.js.map +1 -0
- package/dist/types/preflight.d.ts +49 -0
- package/dist/types/preflight.d.ts.map +1 -0
- package/dist/types/preflight.js +8 -0
- package/dist/types/preflight.js.map +1 -0
- package/dist/types/report.d.ts +161 -0
- package/dist/types/report.d.ts.map +1 -0
- package/dist/types/report.js +8 -0
- package/dist/types/report.js.map +1 -0
- package/dist/types/reviewer.d.ts +66 -0
- package/dist/types/reviewer.d.ts.map +1 -0
- package/dist/types/reviewer.js +5 -0
- package/dist/types/reviewer.js.map +1 -0
- package/dist/types/state.d.ts +124 -0
- package/dist/types/state.d.ts.map +1 -0
- package/dist/types/state.js +20 -0
- package/dist/types/state.js.map +1 -0
- package/dist/types/task.d.ts +117 -0
- package/dist/types/task.d.ts.map +1 -0
- package/dist/types/task.js +7 -0
- package/dist/types/task.js.map +1 -0
- package/dist/types/workspace_state.d.ts +125 -0
- package/dist/types/workspace_state.d.ts.map +1 -0
- package/dist/types/workspace_state.js +10 -0
- package/dist/types/workspace_state.js.map +1 -0
- package/envoi.config.json +191 -0
- package/package.json +52 -0
- package/relais/prompts/.gitkeep +0 -0
- package/relais/prompts/builder.system.txt +13 -0
- package/relais/prompts/builder.user.txt +15 -0
- package/relais/prompts/orchestrator.system.txt +37 -0
- package/relais/prompts/orchestrator.user.txt +34 -0
- package/relais/prompts/reviewer.system.txt +33 -0
- package/relais/prompts/reviewer.user.txt +35 -0
- package/relais/schemas/.gitkeep +0 -0
- package/relais/schemas/builder_result.schema.json +29 -0
- package/relais/schemas/report.schema.json +195 -0
- package/relais/schemas/reviewer_result.schema.json +70 -0
- package/relais/schemas/task.schema.json +155 -0
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Judge phase: Compute touched files from git diff.
|
|
3
|
+
*
|
|
4
|
+
* Provides functions to analyze git changes and categorize files
|
|
5
|
+
* for scope guardrail enforcement.
|
|
6
|
+
*/
|
|
7
|
+
import { execSync } from 'node:child_process';
|
|
8
|
+
import micromatch from 'micromatch';
|
|
9
|
+
/**
|
|
10
|
+
* Parses git diff --name-status output into categorized file lists.
|
|
11
|
+
*
|
|
12
|
+
* Git diff --name-status format:
|
|
13
|
+
* - M <path> - Modified
|
|
14
|
+
* - A <path> - Added
|
|
15
|
+
* - D <path> - Deleted
|
|
16
|
+
* - R<score> <old> <new> - Renamed (tab-separated, e.g., R100\told\tnew)
|
|
17
|
+
*
|
|
18
|
+
* This is a pure function that can be easily tested.
|
|
19
|
+
*
|
|
20
|
+
* @param output - Raw output from `git diff --name-status <base>...HEAD`
|
|
21
|
+
* @returns Object with categorized file lists
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```typescript
|
|
25
|
+
* const diffOutput = 'M\tfile1.ts\nA\tfile2.ts\nR100\told.ts\tnew.ts\n';
|
|
26
|
+
* const result = parseGitDiffNameStatus(diffOutput);
|
|
27
|
+
* // result.modified = ['file1.ts']
|
|
28
|
+
* // result.added = ['file2.ts']
|
|
29
|
+
* // result.renamed = [{ from: 'old.ts', to: 'new.ts' }]
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export function parseGitDiffNameStatus(output) {
|
|
33
|
+
const modified = [];
|
|
34
|
+
const added = [];
|
|
35
|
+
const deleted = [];
|
|
36
|
+
const renamed = [];
|
|
37
|
+
const trimmed = output.trim();
|
|
38
|
+
if (trimmed === '') {
|
|
39
|
+
return { modified, added, deleted, renamed };
|
|
40
|
+
}
|
|
41
|
+
const lines = trimmed.split('\n').filter((line) => line.length > 0);
|
|
42
|
+
for (const line of lines) {
|
|
43
|
+
// Handle renamed files: R<score>\t<old>\t<new>
|
|
44
|
+
// The similarity score can be 0-100, so we check if line starts with R
|
|
45
|
+
if (line.startsWith('R')) {
|
|
46
|
+
// Split by tab to get: [R<score>, old, new]
|
|
47
|
+
const parts = line.split('\t');
|
|
48
|
+
if (parts.length >= 3) {
|
|
49
|
+
const from = parts[1];
|
|
50
|
+
const to = parts[2];
|
|
51
|
+
renamed.push({ from, to });
|
|
52
|
+
}
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
// For other status codes, format is: <code>\t<path>
|
|
56
|
+
const parts = line.split('\t');
|
|
57
|
+
if (parts.length < 2) {
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
const status = parts[0];
|
|
61
|
+
const path = parts[1];
|
|
62
|
+
switch (status) {
|
|
63
|
+
case 'M':
|
|
64
|
+
modified.push(path);
|
|
65
|
+
break;
|
|
66
|
+
case 'A':
|
|
67
|
+
added.push(path);
|
|
68
|
+
break;
|
|
69
|
+
case 'D':
|
|
70
|
+
deleted.push(path);
|
|
71
|
+
break;
|
|
72
|
+
// Ignore other status codes (C for copy, etc.)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return { modified, added, deleted, renamed };
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Gets all files touched since a base commit using git diff --name-status
|
|
79
|
+
* and git status --porcelain.
|
|
80
|
+
*
|
|
81
|
+
* Combines:
|
|
82
|
+
* - Tracked file changes (modified, added, deleted, renamed) from git diff
|
|
83
|
+
* - Untracked files from git status
|
|
84
|
+
*
|
|
85
|
+
* @param baseCommit - The base commit SHA to diff against (e.g., 'main' or commit hash)
|
|
86
|
+
* @returns TouchedFiles object with categorized file lists
|
|
87
|
+
* @throws {Error} If git commands fail
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* ```typescript
|
|
91
|
+
* const touched = getTouchedFiles('main');
|
|
92
|
+
* console.log(`Modified: ${touched.modified.length}`);
|
|
93
|
+
* console.log(`All files: ${touched.all.join(', ')}`);
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
export function getTouchedFiles(baseCommit) {
|
|
97
|
+
// Get tracked file changes from git diff
|
|
98
|
+
let diffStatus;
|
|
99
|
+
try {
|
|
100
|
+
const diffOutput = execSync(`git diff --name-status ${baseCommit}...HEAD`, {
|
|
101
|
+
encoding: 'utf-8',
|
|
102
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
103
|
+
});
|
|
104
|
+
diffStatus = parseGitDiffNameStatus(diffOutput);
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
throw new Error(`Failed to get git diff from ${baseCommit}: ${error instanceof Error ? error.message : String(error)}`);
|
|
108
|
+
}
|
|
109
|
+
// Get untracked files from git status
|
|
110
|
+
let untracked = [];
|
|
111
|
+
try {
|
|
112
|
+
const statusOutput = execSync('git status --porcelain', {
|
|
113
|
+
encoding: 'utf-8',
|
|
114
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
115
|
+
});
|
|
116
|
+
// Untracked files have ?? prefix
|
|
117
|
+
untracked = statusOutput
|
|
118
|
+
.trim()
|
|
119
|
+
.split('\n')
|
|
120
|
+
.filter((line) => line.startsWith('??'))
|
|
121
|
+
.map((line) => line.substring(3).trim()); // Remove "?? " prefix
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
// If git status fails, continue without untracked files
|
|
125
|
+
// (this is less critical than diff failure)
|
|
126
|
+
}
|
|
127
|
+
// Compute union of all file paths (excluding deleted files)
|
|
128
|
+
const all = [
|
|
129
|
+
...diffStatus.modified,
|
|
130
|
+
...diffStatus.added,
|
|
131
|
+
...diffStatus.renamed.map((r) => r.to),
|
|
132
|
+
...untracked,
|
|
133
|
+
];
|
|
134
|
+
return {
|
|
135
|
+
modified: diffStatus.modified,
|
|
136
|
+
added: diffStatus.added,
|
|
137
|
+
deleted: diffStatus.deleted,
|
|
138
|
+
renamed: diffStatus.renamed,
|
|
139
|
+
untracked,
|
|
140
|
+
all,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Checks if a path matches any of the given glob patterns.
|
|
145
|
+
*
|
|
146
|
+
* @param path - The file path to check
|
|
147
|
+
* @param patterns - Array of glob patterns to match against
|
|
148
|
+
* @returns True if path matches any pattern, false otherwise
|
|
149
|
+
*/
|
|
150
|
+
function matchesGlob(path, patterns) {
|
|
151
|
+
if (patterns.length === 0) {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
return micromatch.isMatch(path, patterns);
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Checks if a path is a lockfile based on scopeConfig.lockfiles list.
|
|
158
|
+
*
|
|
159
|
+
* @param path - The file path to check
|
|
160
|
+
* @param lockfiles - Array of lockfile names/patterns
|
|
161
|
+
* @returns True if path matches any lockfile pattern
|
|
162
|
+
*/
|
|
163
|
+
function isLockfile(path, lockfiles) {
|
|
164
|
+
if (lockfiles.length === 0) {
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
// Check if path ends with any lockfile name, or matches lockfile globs
|
|
168
|
+
return lockfiles.some((lockfile) => {
|
|
169
|
+
// If lockfile is a simple name (e.g., "package-lock.json"), check if path ends with it
|
|
170
|
+
if (!lockfile.includes('/') && !lockfile.includes('*')) {
|
|
171
|
+
return path.endsWith(lockfile);
|
|
172
|
+
}
|
|
173
|
+
// Otherwise, treat as glob pattern
|
|
174
|
+
return micromatch.isMatch(path, [lockfile]);
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Checks touched files against scope rules and returns first violation or success.
|
|
179
|
+
*
|
|
180
|
+
* Checks are performed in priority order:
|
|
181
|
+
* 1. Runner-owned mutation (highest priority)
|
|
182
|
+
* 2. Forbidden globs
|
|
183
|
+
* 3. Outside allowed globs
|
|
184
|
+
* 4. New file when not allowed
|
|
185
|
+
* 5. Lockfile change when not allowed
|
|
186
|
+
*
|
|
187
|
+
* Returns the first violation found, or success if no violations.
|
|
188
|
+
*
|
|
189
|
+
* @param touched - TouchedFiles object with categorized file lists
|
|
190
|
+
* @param taskScope - Task scope configuration with allowed/forbidden globs and permissions
|
|
191
|
+
* @param scopeConfig - Scope configuration with lockfiles list
|
|
192
|
+
* @param runnerOwnedGlobs - Glob patterns for files owned by the runner
|
|
193
|
+
* @returns ScopeCheckResult with stopCode and violatingFiles
|
|
194
|
+
*
|
|
195
|
+
* @example
|
|
196
|
+
* ```typescript
|
|
197
|
+
* const result = checkScopeViolations(
|
|
198
|
+
* { modified: ['src/utils.ts'], added: ['src/new.ts'], deleted: [], renamed: [], untracked: ['src/new.ts'], all: ['src/utils.ts', 'src/new.ts'] },
|
|
199
|
+
* { allowed_globs: ['src/**'], forbidden_globs: ['*.key'], allow_new_files: false, allow_lockfile_changes: true },
|
|
200
|
+
* { lockfiles: ['package-lock.json'], default_allowed_globs: [], default_forbidden_globs: [], default_allow_new_files: true, default_allow_lockfile_changes: false },
|
|
201
|
+
* ['pilot/**']
|
|
202
|
+
* );
|
|
203
|
+
* if (!result.ok) {
|
|
204
|
+
* console.error(`Violation: ${result.stopCode}`, result.violatingFiles);
|
|
205
|
+
* }
|
|
206
|
+
* ```
|
|
207
|
+
*/
|
|
208
|
+
export function checkScopeViolations(touched, taskScope, scopeConfig, runnerOwnedGlobs) {
|
|
209
|
+
// Get all touched paths (excluding deleted files)
|
|
210
|
+
const allPaths = touched.all;
|
|
211
|
+
const untrackedSet = new Set(touched.untracked);
|
|
212
|
+
const newFilesSet = new Set([...touched.added, ...touched.untracked, ...touched.renamed.map((r) => r.to)]);
|
|
213
|
+
// Check 1: Runner-owned mutation (highest priority)
|
|
214
|
+
const runnerOwnedViolations = [];
|
|
215
|
+
for (const path of allPaths) {
|
|
216
|
+
if (matchesGlob(path, runnerOwnedGlobs)) {
|
|
217
|
+
runnerOwnedViolations.push(path);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if (runnerOwnedViolations.length > 0) {
|
|
221
|
+
return {
|
|
222
|
+
ok: false,
|
|
223
|
+
stopCode: 'STOP_RUNNER_OWNED_MUTATION',
|
|
224
|
+
violatingFiles: runnerOwnedViolations,
|
|
225
|
+
reason: `Files match runner-owned globs: ${runnerOwnedViolations.join(', ')}`,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
// Check 2: Forbidden globs
|
|
229
|
+
const forbiddenViolations = [];
|
|
230
|
+
for (const path of allPaths) {
|
|
231
|
+
if (matchesGlob(path, taskScope.forbidden_globs)) {
|
|
232
|
+
forbiddenViolations.push(path);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if (forbiddenViolations.length > 0) {
|
|
236
|
+
return {
|
|
237
|
+
ok: false,
|
|
238
|
+
stopCode: 'STOP_SCOPE_VIOLATION_FORBIDDEN',
|
|
239
|
+
violatingFiles: forbiddenViolations,
|
|
240
|
+
reason: `Files match forbidden glob patterns: ${forbiddenViolations.join(', ')}`,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
// Check 3: Outside allowed globs
|
|
244
|
+
if (taskScope.allowed_globs.length > 0) {
|
|
245
|
+
const outsideAllowedViolations = [];
|
|
246
|
+
for (const path of allPaths) {
|
|
247
|
+
if (!matchesGlob(path, taskScope.allowed_globs)) {
|
|
248
|
+
outsideAllowedViolations.push(path);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
if (outsideAllowedViolations.length > 0) {
|
|
252
|
+
return {
|
|
253
|
+
ok: false,
|
|
254
|
+
stopCode: 'STOP_SCOPE_VIOLATION_OUTSIDE_ALLOWED',
|
|
255
|
+
violatingFiles: outsideAllowedViolations,
|
|
256
|
+
reason: `Files do not match any allowed glob pattern: ${outsideAllowedViolations.join(', ')}`,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
// Check 4: New file when not allowed
|
|
261
|
+
if (!taskScope.allow_new_files) {
|
|
262
|
+
const newFileViolations = [];
|
|
263
|
+
for (const path of allPaths) {
|
|
264
|
+
if (newFilesSet.has(path)) {
|
|
265
|
+
newFileViolations.push(path);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
if (newFileViolations.length > 0) {
|
|
269
|
+
return {
|
|
270
|
+
ok: false,
|
|
271
|
+
stopCode: 'STOP_SCOPE_VIOLATION_NEW_FILE',
|
|
272
|
+
violatingFiles: newFileViolations,
|
|
273
|
+
reason: `New files created but allow_new_files is false: ${newFileViolations.join(', ')}`,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
// Check 5: Lockfile change when not allowed
|
|
278
|
+
if (!taskScope.allow_lockfile_changes) {
|
|
279
|
+
const lockfileViolations = [];
|
|
280
|
+
for (const path of allPaths) {
|
|
281
|
+
if (isLockfile(path, scopeConfig.lockfiles)) {
|
|
282
|
+
lockfileViolations.push(path);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
if (lockfileViolations.length > 0) {
|
|
286
|
+
return {
|
|
287
|
+
ok: false,
|
|
288
|
+
stopCode: 'STOP_LOCKFILE_CHANGE_FORBIDDEN',
|
|
289
|
+
violatingFiles: lockfileViolations,
|
|
290
|
+
reason: `Lockfiles modified but allow_lockfile_changes is false: ${lockfileViolations.join(', ')}`,
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
// No violations found
|
|
295
|
+
return {
|
|
296
|
+
ok: true,
|
|
297
|
+
stopCode: null,
|
|
298
|
+
violatingFiles: [],
|
|
299
|
+
reason: null,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Parses git diff --stat output to extract line counts.
|
|
304
|
+
*
|
|
305
|
+
* Git diff --stat format:
|
|
306
|
+
* - Individual file lines: " file.ts | N +++++" or " file.ts | N +++---"
|
|
307
|
+
* - Summary line: "N files changed, M insertions(+), K deletions(-)"
|
|
308
|
+
*
|
|
309
|
+
* This function extracts the summary line totals.
|
|
310
|
+
*
|
|
311
|
+
* @param output - Raw output from `git diff --stat <base>...HEAD`
|
|
312
|
+
* @returns Object with linesAdded and linesDeleted totals
|
|
313
|
+
*
|
|
314
|
+
* @example
|
|
315
|
+
* ```typescript
|
|
316
|
+
* const diffStat = ' file1.ts | 5 +++++\n file2.ts | 3 ---\n 2 files changed, 5 insertions(+), 3 deletions(-)';
|
|
317
|
+
* const result = parseGitDiffStat(diffStat);
|
|
318
|
+
* // result.linesAdded = 5
|
|
319
|
+
* // result.linesDeleted = 3
|
|
320
|
+
* ```
|
|
321
|
+
*/
|
|
322
|
+
export function parseGitDiffStat(output) {
|
|
323
|
+
const trimmed = output.trim();
|
|
324
|
+
if (trimmed === '') {
|
|
325
|
+
return { linesAdded: 0, linesDeleted: 0 };
|
|
326
|
+
}
|
|
327
|
+
const lines = trimmed.split('\n');
|
|
328
|
+
// The summary line is typically the last line
|
|
329
|
+
// Format: "N files changed, M insertions(+), K deletions(-)"
|
|
330
|
+
const summaryLine = lines[lines.length - 1];
|
|
331
|
+
// Match pattern: "N files changed, M insertions(+), K deletions(-)"
|
|
332
|
+
// Or: "N files changed, M insertions(+)" (no deletions)
|
|
333
|
+
// Or: "N files changed, K deletions(-)" (no insertions)
|
|
334
|
+
const filesChangedMatch = summaryLine.match(/(\d+)\s+files?\s+changed/);
|
|
335
|
+
if (!filesChangedMatch) {
|
|
336
|
+
// If no summary line found, return zeros
|
|
337
|
+
return { linesAdded: 0, linesDeleted: 0 };
|
|
338
|
+
}
|
|
339
|
+
// Extract insertions
|
|
340
|
+
const insertionsMatch = summaryLine.match(/(\d+)\s+insertions?\(\+\)/);
|
|
341
|
+
const linesAdded = insertionsMatch ? parseInt(insertionsMatch[1], 10) : 0;
|
|
342
|
+
// Extract deletions
|
|
343
|
+
const deletionsMatch = summaryLine.match(/(\d+)\s+deletions?\(-\)/);
|
|
344
|
+
const linesDeleted = deletionsMatch ? parseInt(deletionsMatch[1], 10) : 0;
|
|
345
|
+
return { linesAdded, linesDeleted };
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Computes blast radius metrics from git diff.
|
|
349
|
+
*
|
|
350
|
+
* Uses git diff --stat to get line counts and TouchedFiles to get file counts.
|
|
351
|
+
*
|
|
352
|
+
* @param baseCommit - The base commit SHA to diff against (e.g., 'main' or commit hash)
|
|
353
|
+
* @param touched - TouchedFiles object with categorized file lists
|
|
354
|
+
* @returns BlastRadius object with file and line counts
|
|
355
|
+
* @throws {Error} If git diff --stat command fails
|
|
356
|
+
*
|
|
357
|
+
* @example
|
|
358
|
+
* ```typescript
|
|
359
|
+
* const touched = getTouchedFiles('main');
|
|
360
|
+
* const blastRadius = computeBlastRadius('main', touched);
|
|
361
|
+
* console.log(`Files touched: ${blastRadius.files_touched}`);
|
|
362
|
+
* console.log(`Lines added: ${blastRadius.lines_added}`);
|
|
363
|
+
* ```
|
|
364
|
+
*/
|
|
365
|
+
export function computeBlastRadius(baseCommit, touched) {
|
|
366
|
+
// Get line counts from git diff --stat
|
|
367
|
+
let linesAdded = 0;
|
|
368
|
+
let linesDeleted = 0;
|
|
369
|
+
try {
|
|
370
|
+
const diffStatOutput = execSync(`git diff --stat ${baseCommit}...HEAD`, {
|
|
371
|
+
encoding: 'utf-8',
|
|
372
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
373
|
+
});
|
|
374
|
+
const parsed = parseGitDiffStat(diffStatOutput);
|
|
375
|
+
linesAdded = parsed.linesAdded;
|
|
376
|
+
linesDeleted = parsed.linesDeleted;
|
|
377
|
+
}
|
|
378
|
+
catch (error) {
|
|
379
|
+
throw new Error(`Failed to get git diff --stat from ${baseCommit}: ${error instanceof Error ? error.message : String(error)}`);
|
|
380
|
+
}
|
|
381
|
+
// Compute file counts from TouchedFiles
|
|
382
|
+
// files_touched = all unique files (modified + added + renamed.to + untracked)
|
|
383
|
+
const filesTouched = touched.all.length;
|
|
384
|
+
// new_files_count = added + untracked + renamed.to
|
|
385
|
+
const newFilesSet = new Set([
|
|
386
|
+
...touched.added,
|
|
387
|
+
...touched.untracked,
|
|
388
|
+
...touched.renamed.map((r) => r.to),
|
|
389
|
+
]);
|
|
390
|
+
const newFilesCount = newFilesSet.size;
|
|
391
|
+
return {
|
|
392
|
+
files_touched: filesTouched,
|
|
393
|
+
lines_added: linesAdded,
|
|
394
|
+
lines_deleted: linesDeleted,
|
|
395
|
+
new_files: newFilesCount,
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Checks blast radius against configured diff limits.
|
|
400
|
+
*
|
|
401
|
+
* Returns STOP_DIFF_TOO_LARGE if either limit is exceeded:
|
|
402
|
+
* - files_touched > max_files_touched
|
|
403
|
+
* - lines_changed (added + deleted) > max_lines_changed
|
|
404
|
+
*
|
|
405
|
+
* @param blastRadius - Blast radius metrics from computeBlastRadius
|
|
406
|
+
* @param limits - Diff limits configuration
|
|
407
|
+
* @returns DiffCheckResult with ok status and stopCode
|
|
408
|
+
*
|
|
409
|
+
* @example
|
|
410
|
+
* ```typescript
|
|
411
|
+
* const blastRadius = computeBlastRadius('main', touched);
|
|
412
|
+
* const result = checkDiffLimits(blastRadius, { max_files_touched: 10, max_lines_changed: 100 });
|
|
413
|
+
* if (!result.ok) {
|
|
414
|
+
* console.error(`Violation: ${result.stopCode}`, result.reason);
|
|
415
|
+
* }
|
|
416
|
+
* ```
|
|
417
|
+
*/
|
|
418
|
+
export function checkDiffLimits(blastRadius, limits) {
|
|
419
|
+
const linesChanged = blastRadius.lines_added + blastRadius.lines_deleted;
|
|
420
|
+
const violations = [];
|
|
421
|
+
if (blastRadius.files_touched > limits.max_files_touched) {
|
|
422
|
+
violations.push(`files_touched (${blastRadius.files_touched}) exceeds max_files_touched (${limits.max_files_touched})`);
|
|
423
|
+
}
|
|
424
|
+
if (linesChanged > limits.max_lines_changed) {
|
|
425
|
+
violations.push(`lines_changed (${linesChanged}) exceeds max_lines_changed (${limits.max_lines_changed})`);
|
|
426
|
+
}
|
|
427
|
+
if (violations.length > 0) {
|
|
428
|
+
return {
|
|
429
|
+
ok: false,
|
|
430
|
+
stopCode: 'STOP_DIFF_TOO_LARGE',
|
|
431
|
+
blastRadius,
|
|
432
|
+
reason: violations.join('; '),
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
return {
|
|
436
|
+
ok: true,
|
|
437
|
+
stopCode: null,
|
|
438
|
+
blastRadius,
|
|
439
|
+
reason: null,
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Checks if HEAD is still at the expected commit (or a descendant of it).
|
|
444
|
+
*
|
|
445
|
+
* - If HEAD === expectedBaseCommit → ok (no changes yet).
|
|
446
|
+
* - If expectedBaseCommit is ancestor of HEAD → ok (builder made commits).
|
|
447
|
+
* - Otherwise → HEAD was moved externally (force push, external commit, branch switch) → STOP_HEAD_MOVED.
|
|
448
|
+
*
|
|
449
|
+
* @param expectedBaseCommit - The base commit SHA stored at tick start
|
|
450
|
+
* @returns HeadCheckResult with ok, stopCode, expectedHead, actualHead, reason
|
|
451
|
+
*/
|
|
452
|
+
export function checkHeadMoved(expectedBaseCommit) {
|
|
453
|
+
let actualHead;
|
|
454
|
+
try {
|
|
455
|
+
actualHead = execSync('git rev-parse HEAD', {
|
|
456
|
+
encoding: 'utf-8',
|
|
457
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
458
|
+
}).trim();
|
|
459
|
+
}
|
|
460
|
+
catch (error) {
|
|
461
|
+
return {
|
|
462
|
+
ok: false,
|
|
463
|
+
stopCode: 'STOP_HEAD_MOVED',
|
|
464
|
+
expectedHead: expectedBaseCommit,
|
|
465
|
+
actualHead: '',
|
|
466
|
+
reason: `Failed to get current HEAD: ${error instanceof Error ? error.message : String(error)}`,
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
if (actualHead === expectedBaseCommit) {
|
|
470
|
+
return {
|
|
471
|
+
ok: true,
|
|
472
|
+
stopCode: null,
|
|
473
|
+
expectedHead: expectedBaseCommit,
|
|
474
|
+
actualHead,
|
|
475
|
+
reason: null,
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
try {
|
|
479
|
+
execSync(`git merge-base --is-ancestor ${expectedBaseCommit} HEAD`, {
|
|
480
|
+
encoding: 'utf-8',
|
|
481
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
482
|
+
});
|
|
483
|
+
return {
|
|
484
|
+
ok: true,
|
|
485
|
+
stopCode: null,
|
|
486
|
+
expectedHead: expectedBaseCommit,
|
|
487
|
+
actualHead,
|
|
488
|
+
reason: null,
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
catch {
|
|
492
|
+
return {
|
|
493
|
+
ok: false,
|
|
494
|
+
stopCode: 'STOP_HEAD_MOVED',
|
|
495
|
+
expectedHead: expectedBaseCommit,
|
|
496
|
+
actualHead,
|
|
497
|
+
reason: `HEAD moved externally: expected ${expectedBaseCommit}, got ${actualHead}`,
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
//# sourceMappingURL=judge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"judge.js","sourceRoot":"","sources":["../../src/lib/judge.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lock mechanism for preventing concurrent envoi runs.
|
|
3
|
+
*
|
|
4
|
+
* Implements crash-safe lock acquisition with boot_id tracking to enable
|
|
5
|
+
* safe reclaim of stale locks after crashes or reboots.
|
|
6
|
+
*/
|
|
7
|
+
import type { LockInfo } from '../types/lock.js';
|
|
8
|
+
/**
|
|
9
|
+
* Error thrown when a lock is already held by another process.
|
|
10
|
+
*/
|
|
11
|
+
export declare class LockHeldError extends Error {
|
|
12
|
+
readonly lockPath: string;
|
|
13
|
+
readonly holder: LockInfo;
|
|
14
|
+
constructor(message: string, lockPath: string, holder: LockInfo);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Error thrown when a lock file is corrupt or malformed.
|
|
18
|
+
*/
|
|
19
|
+
export declare class LockCorruptError extends Error {
|
|
20
|
+
readonly lockPath: string;
|
|
21
|
+
constructor(message: string, lockPath: string);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Gets a unique identifier for the current boot session.
|
|
25
|
+
*
|
|
26
|
+
* On Linux, reads /proc/sys/kernel/random/boot_id.
|
|
27
|
+
* On macOS, uses system uptime + hostname as a fingerprint since macOS
|
|
28
|
+
* doesn't have boot_id but uptime.boot_time is stable per boot.
|
|
29
|
+
*
|
|
30
|
+
* The result is cached for the lifetime of the process.
|
|
31
|
+
*
|
|
32
|
+
* @returns A string uniquely identifying the current boot session
|
|
33
|
+
*/
|
|
34
|
+
export declare function getBootId(): string;
|
|
35
|
+
/**
|
|
36
|
+
* Checks if a process with the given PID is currently running.
|
|
37
|
+
*
|
|
38
|
+
* Uses process.kill(pid, 0) which checks for process existence
|
|
39
|
+
* without actually sending a signal.
|
|
40
|
+
*
|
|
41
|
+
* @param pid - The process ID to check
|
|
42
|
+
* @returns true if the process is running, false otherwise
|
|
43
|
+
*/
|
|
44
|
+
export declare function isPidRunning(pid: number): boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Determines if a lock is stale and can be reclaimed.
|
|
47
|
+
*
|
|
48
|
+
* A lock is considered stale if:
|
|
49
|
+
* - The holding process is no longer running, OR
|
|
50
|
+
* - The boot_id differs from the current boot (system has rebooted)
|
|
51
|
+
*
|
|
52
|
+
* @param lock - The lock information to check
|
|
53
|
+
* @returns true if the lock is stale and can be reclaimed
|
|
54
|
+
*/
|
|
55
|
+
export declare function isLockStale(lock: LockInfo): boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Acquires a lock by creating a lock file atomically.
|
|
58
|
+
*
|
|
59
|
+
* If a lock already exists:
|
|
60
|
+
* - If stale (process dead or different boot), reclaims it with a warning
|
|
61
|
+
* - If held by an active process, throws LockHeldError
|
|
62
|
+
*
|
|
63
|
+
* @param lockPath - Path to the lock file
|
|
64
|
+
* @returns The lock information that was written
|
|
65
|
+
* @throws {LockHeldError} If the lock is held by another active process
|
|
66
|
+
* @throws {LockCorruptError} If the lock file is corrupt or malformed
|
|
67
|
+
* @throws {AtomicFsError} If the lock file cannot be written
|
|
68
|
+
*/
|
|
69
|
+
export declare function acquireLock(lockPath: string): Promise<LockInfo>;
|
|
70
|
+
/**
|
|
71
|
+
* Releases a lock by deleting the lock file.
|
|
72
|
+
*
|
|
73
|
+
* Only releases the lock if it belongs to the current process.
|
|
74
|
+
* Silently succeeds if the lock doesn't exist or belongs to another process.
|
|
75
|
+
*
|
|
76
|
+
* @param lockPath - Path to the lock file
|
|
77
|
+
*/
|
|
78
|
+
export declare function releaseLock(lockPath: string): Promise<void>;
|
|
79
|
+
//# sourceMappingURL=lock.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lock.d.ts","sourceRoot":"","sources":["../../src/lib/lock.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAGjD;;GAEG;AACH,qBAAa,aAAc,SAAQ,KAAK;aAGpB,QAAQ,EAAE,MAAM;aAChB,MAAM,EAAE,QAAQ;gBAFhC,OAAO,EAAE,MAAM,EACC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,QAAQ;CAKnC;AAED;;GAEG;AACH,qBAAa,gBAAiB,SAAQ,KAAK;aAGvB,QAAQ,EAAE,MAAM;gBADhC,OAAO,EAAE,MAAM,EACC,QAAQ,EAAE,MAAM;CAKnC;AAKD;;;;;;;;;;GAUG;AACH,wBAAgB,SAAS,IAAI,MAAM,CAwClC;AAED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAgBjD;AAED;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAQnD;AA+CD;;;;;;;;;;;;GAYG;AACH,wBAAsB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CA4DrE;AAED;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAoBjE"}
|