@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,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task fingerprinting utilities.
|
|
3
|
+
*
|
|
4
|
+
* Task fingerprint is used to detect identical re-dispatch loops.
|
|
5
|
+
* If orchestrator 'retries' without changing the plan, fingerprint stays same and runner stops.
|
|
6
|
+
*/
|
|
7
|
+
import { createHash } from 'node:crypto';
|
|
8
|
+
/**
|
|
9
|
+
* Top-level fields to include in the fingerprint computation.
|
|
10
|
+
*/
|
|
11
|
+
const TOP_LEVEL_FIELDS = [
|
|
12
|
+
'goal',
|
|
13
|
+
'subtasks',
|
|
14
|
+
'acceptance',
|
|
15
|
+
'verify',
|
|
16
|
+
'implementation',
|
|
17
|
+
'risk',
|
|
18
|
+
'notes',
|
|
19
|
+
];
|
|
20
|
+
/**
|
|
21
|
+
* Scope fields to include in the fingerprint computation.
|
|
22
|
+
*/
|
|
23
|
+
const SCOPE_FIELDS = [
|
|
24
|
+
'write',
|
|
25
|
+
'create_under',
|
|
26
|
+
'forbidden',
|
|
27
|
+
'read_forbidden',
|
|
28
|
+
];
|
|
29
|
+
/**
|
|
30
|
+
* Fields to exclude from fingerprint computation.
|
|
31
|
+
*/
|
|
32
|
+
const EXCLUDED_FIELDS = new Set(['task_id', 'id', 'v', 'milestone', 'context']);
|
|
33
|
+
/**
|
|
34
|
+
* Recursively processes an object to trim strings and ensure canonical form.
|
|
35
|
+
*/
|
|
36
|
+
function processValue(value) {
|
|
37
|
+
if (value === null || value === undefined) {
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
40
|
+
if (typeof value === 'string') {
|
|
41
|
+
return value.trim();
|
|
42
|
+
}
|
|
43
|
+
if (Array.isArray(value)) {
|
|
44
|
+
return value.map(processValue);
|
|
45
|
+
}
|
|
46
|
+
if (typeof value === 'object') {
|
|
47
|
+
const processed = {};
|
|
48
|
+
const sortedKeys = Object.keys(value).sort();
|
|
49
|
+
for (const key of sortedKeys) {
|
|
50
|
+
processed[key] = processValue(value[key]);
|
|
51
|
+
}
|
|
52
|
+
return processed;
|
|
53
|
+
}
|
|
54
|
+
return value;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Extracts fingerprint-relevant fields from a task object.
|
|
58
|
+
*/
|
|
59
|
+
function extractFingerprintFields(task) {
|
|
60
|
+
const result = {};
|
|
61
|
+
// Extract top-level fields
|
|
62
|
+
for (const field of TOP_LEVEL_FIELDS) {
|
|
63
|
+
if (field in task && !EXCLUDED_FIELDS.has(field)) {
|
|
64
|
+
result[field] = task[field];
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// Extract scope fields if scope exists
|
|
68
|
+
if (task.scope && typeof task.scope === 'object') {
|
|
69
|
+
const scope = task.scope;
|
|
70
|
+
const scopeResult = {};
|
|
71
|
+
for (const field of SCOPE_FIELDS) {
|
|
72
|
+
if (field in scope) {
|
|
73
|
+
scopeResult[field] = scope[field];
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// Only add scope if it has at least one field
|
|
77
|
+
if (Object.keys(scopeResult).length > 0) {
|
|
78
|
+
result.scope = scopeResult;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return result;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Canonicalizes a task object to a stable JSON string.
|
|
85
|
+
*
|
|
86
|
+
* The canonical form:
|
|
87
|
+
* - Has sorted keys (alphabetical order)
|
|
88
|
+
* - Has trimmed strings
|
|
89
|
+
* - Excludes task_id, id, v, milestone, and context fields
|
|
90
|
+
* - Only includes fingerprint-relevant fields
|
|
91
|
+
*
|
|
92
|
+
* @param task - The task object to canonicalize
|
|
93
|
+
* @returns A canonical JSON string representation
|
|
94
|
+
*/
|
|
95
|
+
export function canonicalizeTask(task) {
|
|
96
|
+
// Extract only fingerprint-relevant fields
|
|
97
|
+
const fingerprintData = extractFingerprintFields(task);
|
|
98
|
+
// Process the data (trim strings, sort keys recursively)
|
|
99
|
+
const processed = processValue(fingerprintData);
|
|
100
|
+
// Convert to JSON with stable key ordering
|
|
101
|
+
// JSON.stringify already produces stable output for objects with sorted keys
|
|
102
|
+
return JSON.stringify(processed);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Computes a SHA-256 fingerprint of a task.
|
|
106
|
+
*
|
|
107
|
+
* @param task - The task object to fingerprint
|
|
108
|
+
* @returns A 64-character hexadecimal SHA-256 hash string
|
|
109
|
+
*/
|
|
110
|
+
export function computeFingerprint(task) {
|
|
111
|
+
const canonical = canonicalizeTask(task);
|
|
112
|
+
const hash = createHash('sha256');
|
|
113
|
+
hash.update(canonical, 'utf8');
|
|
114
|
+
return hash.digest('hex');
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=fingerprint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fingerprint.js","sourceRoot":"","sources":["../../src/lib/fingerprint.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/fs.d.ts
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Atomic file system utilities for crash-safe JSON operations.
|
|
3
|
+
*
|
|
4
|
+
* These utilities implement the write-tmp-fsync-rename pattern to ensure
|
|
5
|
+
* atomic file writes that survive crashes and power failures on POSIX systems.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Result of glob pattern safety check.
|
|
9
|
+
*/
|
|
10
|
+
export type GlobSafetyResult = {
|
|
11
|
+
safe: true;
|
|
12
|
+
} | {
|
|
13
|
+
safe: false;
|
|
14
|
+
reason: string;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Checks if a glob pattern is safe for file deletion operations.
|
|
18
|
+
*
|
|
19
|
+
* Safe patterns:
|
|
20
|
+
* - Relative paths like `envoi/*.tmp`, `envoi/**\/*.tmp`, `*.tmp`
|
|
21
|
+
*
|
|
22
|
+
* Unsafe patterns:
|
|
23
|
+
* - Empty or whitespace-only patterns
|
|
24
|
+
* - Patterns containing path traversal (`..`)
|
|
25
|
+
* - Unix absolute paths (starting with `/`)
|
|
26
|
+
* - Windows absolute paths (e.g., `C:\`)
|
|
27
|
+
* - UNC paths (`\\server\share` or `//server/share`)
|
|
28
|
+
*
|
|
29
|
+
* @param pattern - The glob pattern to check
|
|
30
|
+
* @returns Result indicating if the pattern is safe
|
|
31
|
+
*/
|
|
32
|
+
export declare function isGlobPatternSafe(pattern: string): GlobSafetyResult;
|
|
33
|
+
/**
|
|
34
|
+
* Error thrown when atomic file operations fail.
|
|
35
|
+
*/
|
|
36
|
+
export declare class AtomicFsError extends Error {
|
|
37
|
+
readonly filePath: string;
|
|
38
|
+
readonly cause?: Error | undefined;
|
|
39
|
+
constructor(message: string, filePath: string, cause?: Error | undefined);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Atomically writes JSON data to a file using the write-tmp-fsync-rename pattern.
|
|
43
|
+
*
|
|
44
|
+
* This ensures that the file is never in a partially-written state, even if the
|
|
45
|
+
* process crashes or the system loses power during the write operation.
|
|
46
|
+
*
|
|
47
|
+
* @param filePath - The path to write the JSON file to
|
|
48
|
+
* @param data - The data to serialize and write
|
|
49
|
+
* @throws {AtomicFsError} If the write operation fails
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```typescript
|
|
53
|
+
* await atomicWriteJson('/path/to/config.json', { version: 1, enabled: true });
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
export declare function atomicWriteJson<T>(filePath: string, data: T): Promise<void>;
|
|
57
|
+
/**
|
|
58
|
+
* Reads and parses a JSON file with proper typing.
|
|
59
|
+
*
|
|
60
|
+
* @param filePath - The path to the JSON file to read
|
|
61
|
+
* @returns The parsed JSON data
|
|
62
|
+
* @throws {AtomicFsError} If the file cannot be read or parsed
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```typescript
|
|
66
|
+
* interface Config {
|
|
67
|
+
* version: number;
|
|
68
|
+
* enabled: boolean;
|
|
69
|
+
* }
|
|
70
|
+
* const config = await atomicReadJson<Config>('/path/to/config.json');
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
export declare function atomicReadJson<T>(filePath: string): Promise<T>;
|
|
74
|
+
/**
|
|
75
|
+
* Cleans up stale .tmp files in a directory.
|
|
76
|
+
*
|
|
77
|
+
* This should be called during startup preflight to remove any .tmp files
|
|
78
|
+
* left behind by interrupted write operations.
|
|
79
|
+
*
|
|
80
|
+
* @param dir - The directory to scan for .tmp files
|
|
81
|
+
* @param pattern - Optional pattern to match (defaults to '*.tmp')
|
|
82
|
+
* @returns List of deleted file paths
|
|
83
|
+
* @throws {AtomicFsError} If the cleanup operation fails
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```typescript
|
|
87
|
+
* // Clean up all .tmp files in /envoi directory
|
|
88
|
+
* const deleted = await cleanupTmpFiles('/envoi');
|
|
89
|
+
* console.log(`Cleaned up ${deleted.length} stale tmp files`);
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
export declare function cleanupTmpFiles(dir: string, pattern?: string): Promise<string[]>;
|
|
93
|
+
//# sourceMappingURL=fs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fs.d.ts","sourceRoot":"","sources":["../../src/lib/fs.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAAE,IAAI,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEhF;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,gBAAgB,CA2BnE;AAED;;GAEG;AACH,qBAAa,aAAc,SAAQ,KAAK;aAGpB,QAAQ,EAAE,MAAM;aAChB,KAAK,CAAC,EAAE,KAAK;gBAF7B,OAAO,EAAE,MAAM,EACC,QAAQ,EAAE,MAAM,EAChB,KAAK,CAAC,EAAE,KAAK,YAAA;CAKhC;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,eAAe,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CA6CjF;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,cAAc,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAWpE;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,SAAS,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CA2BtF"}
|
package/dist/lib/fs.js
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Atomic file system utilities for crash-safe JSON operations.
|
|
3
|
+
*
|
|
4
|
+
* These utilities implement the write-tmp-fsync-rename pattern to ensure
|
|
5
|
+
* atomic file writes that survive crashes and power failures on POSIX systems.
|
|
6
|
+
*/
|
|
7
|
+
import { open, rename, unlink, readFile, readdir } from 'node:fs/promises';
|
|
8
|
+
import { join } from 'node:path';
|
|
9
|
+
/**
|
|
10
|
+
* Checks if a glob pattern is safe for file deletion operations.
|
|
11
|
+
*
|
|
12
|
+
* Safe patterns:
|
|
13
|
+
* - Relative paths like `envoi/*.tmp`, `envoi/**\/*.tmp`, `*.tmp`
|
|
14
|
+
*
|
|
15
|
+
* Unsafe patterns:
|
|
16
|
+
* - Empty or whitespace-only patterns
|
|
17
|
+
* - Patterns containing path traversal (`..`)
|
|
18
|
+
* - Unix absolute paths (starting with `/`)
|
|
19
|
+
* - Windows absolute paths (e.g., `C:\`)
|
|
20
|
+
* - UNC paths (`\\server\share` or `//server/share`)
|
|
21
|
+
*
|
|
22
|
+
* @param pattern - The glob pattern to check
|
|
23
|
+
* @returns Result indicating if the pattern is safe
|
|
24
|
+
*/
|
|
25
|
+
export function isGlobPatternSafe(pattern) {
|
|
26
|
+
// Check for empty or whitespace-only patterns
|
|
27
|
+
if (!pattern || pattern.trim() === '') {
|
|
28
|
+
return { safe: false, reason: 'Empty or whitespace-only pattern' };
|
|
29
|
+
}
|
|
30
|
+
// Check for path traversal
|
|
31
|
+
if (pattern.includes('..')) {
|
|
32
|
+
return { safe: false, reason: 'Pattern contains path traversal (..)' };
|
|
33
|
+
}
|
|
34
|
+
// Check for Unix absolute paths
|
|
35
|
+
if (pattern.startsWith('/')) {
|
|
36
|
+
return { safe: false, reason: 'Pattern is an absolute Unix path' };
|
|
37
|
+
}
|
|
38
|
+
// Check for Windows absolute paths (e.g., C:\, D:\)
|
|
39
|
+
if (/^[A-Za-z]:[\\\/]/.test(pattern)) {
|
|
40
|
+
return { safe: false, reason: 'Pattern is an absolute Windows path' };
|
|
41
|
+
}
|
|
42
|
+
// Check for UNC paths (\\server\share or //server/share)
|
|
43
|
+
if (pattern.startsWith('\\\\') || pattern.startsWith('//')) {
|
|
44
|
+
return { safe: false, reason: 'Pattern is a UNC path' };
|
|
45
|
+
}
|
|
46
|
+
return { safe: true };
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Error thrown when atomic file operations fail.
|
|
50
|
+
*/
|
|
51
|
+
export class AtomicFsError extends Error {
|
|
52
|
+
filePath;
|
|
53
|
+
cause;
|
|
54
|
+
constructor(message, filePath, cause) {
|
|
55
|
+
super(message);
|
|
56
|
+
this.filePath = filePath;
|
|
57
|
+
this.cause = cause;
|
|
58
|
+
this.name = 'AtomicFsError';
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Atomically writes JSON data to a file using the write-tmp-fsync-rename pattern.
|
|
63
|
+
*
|
|
64
|
+
* This ensures that the file is never in a partially-written state, even if the
|
|
65
|
+
* process crashes or the system loses power during the write operation.
|
|
66
|
+
*
|
|
67
|
+
* @param filePath - The path to write the JSON file to
|
|
68
|
+
* @param data - The data to serialize and write
|
|
69
|
+
* @throws {AtomicFsError} If the write operation fails
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```typescript
|
|
73
|
+
* await atomicWriteJson('/path/to/config.json', { version: 1, enabled: true });
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
export async function atomicWriteJson(filePath, data) {
|
|
77
|
+
const tmpPath = `${filePath}.tmp`;
|
|
78
|
+
let fileHandle = null;
|
|
79
|
+
try {
|
|
80
|
+
// Serialize with 2-space indent for readability
|
|
81
|
+
const content = JSON.stringify(data, null, 2) + '\n';
|
|
82
|
+
// Open file for writing, create if doesn't exist, truncate if exists
|
|
83
|
+
fileHandle = await open(tmpPath, 'w');
|
|
84
|
+
// Write the content
|
|
85
|
+
await fileHandle.writeFile(content, 'utf-8');
|
|
86
|
+
// fsync to ensure data is flushed to disk
|
|
87
|
+
await fileHandle.sync();
|
|
88
|
+
// Close before rename
|
|
89
|
+
await fileHandle.close();
|
|
90
|
+
fileHandle = null;
|
|
91
|
+
// Atomic rename (POSIX guarantees atomicity)
|
|
92
|
+
await rename(tmpPath, filePath);
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
// Attempt to clean up the tmp file on error
|
|
96
|
+
if (fileHandle) {
|
|
97
|
+
try {
|
|
98
|
+
await fileHandle.close();
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
// Ignore close errors during cleanup
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
await unlink(tmpPath);
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
// Ignore unlink errors - file may not exist
|
|
109
|
+
}
|
|
110
|
+
throw new AtomicFsError(`Failed to atomically write JSON to ${filePath}: ${error instanceof Error ? error.message : String(error)}`, filePath, error instanceof Error ? error : undefined);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Reads and parses a JSON file with proper typing.
|
|
115
|
+
*
|
|
116
|
+
* @param filePath - The path to the JSON file to read
|
|
117
|
+
* @returns The parsed JSON data
|
|
118
|
+
* @throws {AtomicFsError} If the file cannot be read or parsed
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* ```typescript
|
|
122
|
+
* interface Config {
|
|
123
|
+
* version: number;
|
|
124
|
+
* enabled: boolean;
|
|
125
|
+
* }
|
|
126
|
+
* const config = await atomicReadJson<Config>('/path/to/config.json');
|
|
127
|
+
* ```
|
|
128
|
+
*/
|
|
129
|
+
export async function atomicReadJson(filePath) {
|
|
130
|
+
try {
|
|
131
|
+
const content = await readFile(filePath, 'utf-8');
|
|
132
|
+
return JSON.parse(content);
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
throw new AtomicFsError(`Failed to read JSON from ${filePath}: ${error instanceof Error ? error.message : String(error)}`, filePath, error instanceof Error ? error : undefined);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Cleans up stale .tmp files in a directory.
|
|
140
|
+
*
|
|
141
|
+
* This should be called during startup preflight to remove any .tmp files
|
|
142
|
+
* left behind by interrupted write operations.
|
|
143
|
+
*
|
|
144
|
+
* @param dir - The directory to scan for .tmp files
|
|
145
|
+
* @param pattern - Optional pattern to match (defaults to '*.tmp')
|
|
146
|
+
* @returns List of deleted file paths
|
|
147
|
+
* @throws {AtomicFsError} If the cleanup operation fails
|
|
148
|
+
*
|
|
149
|
+
* @example
|
|
150
|
+
* ```typescript
|
|
151
|
+
* // Clean up all .tmp files in /envoi directory
|
|
152
|
+
* const deleted = await cleanupTmpFiles('/envoi');
|
|
153
|
+
* console.log(`Cleaned up ${deleted.length} stale tmp files`);
|
|
154
|
+
* ```
|
|
155
|
+
*/
|
|
156
|
+
export async function cleanupTmpFiles(dir, pattern = '.tmp') {
|
|
157
|
+
const deleted = [];
|
|
158
|
+
try {
|
|
159
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
160
|
+
for (const entry of entries) {
|
|
161
|
+
if (entry.isFile() && entry.name.endsWith(pattern)) {
|
|
162
|
+
const filePath = join(dir, entry.name);
|
|
163
|
+
try {
|
|
164
|
+
await unlink(filePath);
|
|
165
|
+
deleted.push(filePath);
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
// Log but continue - we want to clean up as many as possible
|
|
169
|
+
console.warn(`Failed to delete tmp file ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return deleted;
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
throw new AtomicFsError(`Failed to cleanup tmp files in ${dir}: ${error instanceof Error ? error.message : String(error)}`, dir, error instanceof Error ? error : undefined);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
//# sourceMappingURL=fs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fs.js","sourceRoot":"","sources":["../../src/lib/fs.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git helper functions for preflight checks and diff tracking.
|
|
3
|
+
*
|
|
4
|
+
* Uses execSync for git operations to ensure synchronous, blocking behavior.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Checks if the current directory is inside a git repository.
|
|
8
|
+
*
|
|
9
|
+
* @returns true if in a git repo, false otherwise
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* if (!isGitRepo()) {
|
|
14
|
+
* console.error('Not in a git repository');
|
|
15
|
+
* }
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export declare function isGitRepo(): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Checks if the git worktree is clean (no uncommitted tracked changes
|
|
21
|
+
* AND no untracked files).
|
|
22
|
+
*
|
|
23
|
+
* A clean worktree means:
|
|
24
|
+
* - No staged changes
|
|
25
|
+
* - No unstaged changes to tracked files
|
|
26
|
+
* - No untracked files
|
|
27
|
+
*
|
|
28
|
+
* @returns true if worktree is clean, false otherwise
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```typescript
|
|
32
|
+
* if (!isWorktreeClean()) {
|
|
33
|
+
* console.error('Worktree has uncommitted changes');
|
|
34
|
+
* }
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export declare function isWorktreeClean(): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Parses git status porcelain output and filters files based on exclusion globs.
|
|
40
|
+
*
|
|
41
|
+
* This is a pure function that can be easily tested.
|
|
42
|
+
*
|
|
43
|
+
* @param statusOutput - Raw output from `git status --porcelain`
|
|
44
|
+
* @param excludeGlobs - Glob patterns for files to exclude from the dirty check
|
|
45
|
+
* @returns Object with `clean` (boolean), `dirtyFiles`, and `excludedFiles`
|
|
46
|
+
*/
|
|
47
|
+
export declare function parseGitStatusWithExclusions(statusOutput: string, excludeGlobs: string[]): {
|
|
48
|
+
clean: boolean;
|
|
49
|
+
dirtyFiles: string[];
|
|
50
|
+
excludedFiles: string[];
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Checks if the git worktree is clean, excluding files matching specified globs.
|
|
54
|
+
*
|
|
55
|
+
* This is useful for ignoring runner-owned files (like REPORT.json, STATE.json)
|
|
56
|
+
* that are expected to change every tick.
|
|
57
|
+
*
|
|
58
|
+
* @param excludeGlobs - Glob patterns for files to exclude from the dirty check
|
|
59
|
+
* @returns Object with `clean` (boolean) and `dirtyFiles` (non-excluded dirty files)
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* ```typescript
|
|
63
|
+
* const result = isWorktreeCleanExcluding(['envoi/**', 'pilot/**']);
|
|
64
|
+
* if (!result.clean) {
|
|
65
|
+
* console.error('Dirty files:', result.dirtyFiles);
|
|
66
|
+
* }
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
export declare function isWorktreeCleanExcluding(excludeGlobs: string[]): {
|
|
70
|
+
clean: boolean;
|
|
71
|
+
dirtyFiles: string[];
|
|
72
|
+
excludedFiles: string[];
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Gets the current HEAD commit SHA.
|
|
76
|
+
*
|
|
77
|
+
* @returns The full 40-character SHA of HEAD
|
|
78
|
+
* @throws {Error} If not in a git repo or HEAD doesn't exist
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* ```typescript
|
|
82
|
+
* const sha = getHeadCommit();
|
|
83
|
+
* console.log(`Current commit: ${sha}`);
|
|
84
|
+
* ```
|
|
85
|
+
*/
|
|
86
|
+
export declare function getHeadCommit(): string;
|
|
87
|
+
/**
|
|
88
|
+
* Gets the list of files that have changed since a base commit.
|
|
89
|
+
*
|
|
90
|
+
* Returns both modified and added files in the diff.
|
|
91
|
+
*
|
|
92
|
+
* @param base - The base commit SHA to diff against
|
|
93
|
+
* @returns Array of file paths that have changed
|
|
94
|
+
* @throws {Error} If the diff operation fails
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* ```typescript
|
|
98
|
+
* const changedFiles = getDiffFiles('abc123');
|
|
99
|
+
* console.log(`${changedFiles.length} files changed`);
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
export declare function getDiffFiles(base: string): string[];
|
|
103
|
+
/**
|
|
104
|
+
* Gets the list of untracked files in the repository.
|
|
105
|
+
*
|
|
106
|
+
* @returns Array of untracked file paths
|
|
107
|
+
*
|
|
108
|
+
* @example
|
|
109
|
+
* ```typescript
|
|
110
|
+
* const untracked = getUntrackedFiles();
|
|
111
|
+
* if (untracked.length > 0) {
|
|
112
|
+
* console.log(`Found ${untracked.length} untracked files`);
|
|
113
|
+
* }
|
|
114
|
+
* ```
|
|
115
|
+
*/
|
|
116
|
+
export declare function getUntrackedFiles(): string[];
|
|
117
|
+
/**
|
|
118
|
+
* Gets the current git branch name.
|
|
119
|
+
*
|
|
120
|
+
* @returns The current branch name
|
|
121
|
+
* @throws {Error} If not in a git repo or branch cannot be determined
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
* ```typescript
|
|
125
|
+
* const branch = getCurrentBranch();
|
|
126
|
+
* console.log(`Current branch: ${branch}`);
|
|
127
|
+
* ```
|
|
128
|
+
*/
|
|
129
|
+
export declare function getCurrentBranch(): string;
|
|
130
|
+
/**
|
|
131
|
+
* Gets the top-level directory of the git repository.
|
|
132
|
+
*
|
|
133
|
+
* @returns The absolute path to the git repository root, or null if not in a git repo
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* ```typescript
|
|
137
|
+
* const root = getGitTopLevel();
|
|
138
|
+
* if (root) {
|
|
139
|
+
* console.log(`Git root: ${root}`);
|
|
140
|
+
* }
|
|
141
|
+
* ```
|
|
142
|
+
*/
|
|
143
|
+
export declare function getGitTopLevel(): string | null;
|
|
144
|
+
/**
|
|
145
|
+
* Stashes pilot/ directory files to prevent merge conflicts.
|
|
146
|
+
*
|
|
147
|
+
* Creates a stash containing only files in the pilot/ directory.
|
|
148
|
+
* This allows safe merging without losing pilot state.
|
|
149
|
+
*
|
|
150
|
+
* @returns The stash reference (e.g., "stash@{0}")
|
|
151
|
+
* @throws {Error} If the stash operation fails
|
|
152
|
+
*
|
|
153
|
+
* @example
|
|
154
|
+
* ```typescript
|
|
155
|
+
* const stashRef = stashPilotFiles();
|
|
156
|
+
* // Perform merge...
|
|
157
|
+
* popPilotStash(stashRef);
|
|
158
|
+
* ```
|
|
159
|
+
*/
|
|
160
|
+
export declare function stashPilotFiles(): string;
|
|
161
|
+
/**
|
|
162
|
+
* Pops a previously created stash to restore pilot files.
|
|
163
|
+
*
|
|
164
|
+
* Restores the stashed pilot/ directory files back to the working tree.
|
|
165
|
+
*
|
|
166
|
+
* @param stashRef - The stash reference returned by stashPilotFiles (e.g., "stash@{0}")
|
|
167
|
+
* @throws {Error} If the stash pop operation fails
|
|
168
|
+
*
|
|
169
|
+
* @example
|
|
170
|
+
* ```typescript
|
|
171
|
+
* const stashRef = stashPilotFiles();
|
|
172
|
+
* // Perform merge...
|
|
173
|
+
* popPilotStash(stashRef);
|
|
174
|
+
* ```
|
|
175
|
+
*/
|
|
176
|
+
export declare function popPilotStash(stashRef: string): void;
|
|
177
|
+
//# sourceMappingURL=git.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../../src/lib/git.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH;;;;;;;;;;;GAWG;AACH,wBAAgB,SAAS,IAAI,OAAO,CAUnC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,eAAe,IAAI,OAAO,CAazC;AAED;;;;;;;;GAQG;AACH,wBAAgB,4BAA4B,CAC1C,YAAY,EAAE,MAAM,EACpB,YAAY,EAAE,MAAM,EAAE,GACrB;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,UAAU,EAAE,MAAM,EAAE,CAAC;IAAC,aAAa,EAAE,MAAM,EAAE,CAAA;CAAE,CA0CnE;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,wBAAwB,CACtC,YAAY,EAAE,MAAM,EAAE,GACrB;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,UAAU,EAAE,MAAM,EAAE,CAAC;IAAC,aAAa,EAAE,MAAM,EAAE,CAAA;CAAE,CAYnE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAYtC;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAenD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,EAAE,CAiB5C;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAYzC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,IAAI,MAAM,GAAG,IAAI,CAU9C;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,eAAe,IAAI,MAAM,CAmCxC;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAWpD"}
|