@macedon-technologies/batman 1.17.3 → 1.18.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.
Files changed (44) hide show
  1. package/.jsii +124 -17
  2. package/.swamp.yaml +6 -0
  3. package/API.md +107 -0
  4. package/CLAUDE.md +44 -0
  5. package/architecture-rules/README.md +42 -0
  6. package/architecture-rules/check.ts +162 -0
  7. package/architecture-rules/rules.json +3 -0
  8. package/architecture-rules/tsconfig.json +14 -0
  9. package/attestation/manifest.ts +40 -0
  10. package/attestation/tsconfig.json +13 -0
  11. package/attestation/verify.ts +142 -0
  12. package/lib/BatmanProject.js +8 -2
  13. package/lib/BootcampProject.js +8 -2
  14. package/lib/CodeOwnersFile.js +1 -1
  15. package/lib/ProductionDeployWorkflow.js +1 -1
  16. package/lib/SoftwareFactoryArchitectureRules.d.ts +10 -0
  17. package/lib/SoftwareFactoryArchitectureRules.js +245 -0
  18. package/lib/SoftwareFactoryAttestation.d.ts +18 -0
  19. package/lib/SoftwareFactoryAttestation.js +221 -0
  20. package/lib/SoftwareFactoryFiles.d.ts +45 -0
  21. package/lib/SoftwareFactoryFiles.js +102 -0
  22. package/lib/SoftwareFactoryModels.d.ts +25 -0
  23. package/lib/SoftwareFactoryModels.js +306 -0
  24. package/lib/SoftwareFactoryOpenSpecGates.d.ts +8 -0
  25. package/lib/SoftwareFactoryOpenSpecGates.js +75 -0
  26. package/lib/SoftwareFactoryWorkflows.d.ts +28 -0
  27. package/lib/SoftwareFactoryWorkflows.js +326 -0
  28. package/lib/StagingDeployWorkflow.js +1 -1
  29. package/lib/WorkflowAutoDiscover.js +1 -1
  30. package/lib/index.d.ts +1 -0
  31. package/lib/index.js +2 -1
  32. package/lib/utils.js +29 -1
  33. package/models/@goodcraft/github/platform-common-pr.yaml +10 -0
  34. package/models/@swamp/software-factory/platform-common-factory.yaml +240 -0
  35. package/models/@webframp/github/platform-common-issues.yaml +8 -0
  36. package/models/command/shell/platform-common-shell-architecture.yaml +8 -0
  37. package/models/command/shell/platform-common-shell-manifest.yaml +8 -0
  38. package/models/command/shell/platform-common-shell.yaml +8 -0
  39. package/openspec-gates/check-tasks-complete.ts +44 -0
  40. package/openspec-gates/tsconfig.json +13 -0
  41. package/package.json +6 -1
  42. package/workflows/workflow-openspec-plan-verify.yaml +51 -0
  43. package/workflows/workflow-openspec-tasks-verify.yaml +51 -0
  44. package/workflows/workflow-pre-pr-verify.yaml +118 -0
@@ -0,0 +1,245 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SoftwareFactoryArchitectureRules = void 0;
4
+ const projen_1 = require("projen");
5
+ /**
6
+ * The generic, data-driven architecture rule engine plus an empty seed
7
+ * rules.json. `check.ts`/`tsconfig.json`/`README.md` are managed (Batman
8
+ * owns the engine); `rules.json` is a `SampleFile` — each repo's own rules
9
+ * are data the team owns, not something Batman should overwrite.
10
+ */
11
+ class SoftwareFactoryArchitectureRules {
12
+ constructor(project) {
13
+ new projen_1.TextFile(project, 'architecture-rules/check.ts', {
14
+ marker: true,
15
+ lines: [
16
+ '/**',
17
+ ' * Generic architecture-rule engine. Rules live entirely in rules.json — this',
18
+ ' * file only knows how to evaluate the check "types" that rules can declare.',
19
+ ' * Add a new rule by adding a rules.json entry; add a new check type by adding',
20
+ ' * a case in evaluateRule().',
21
+ ' */',
22
+ "import * as fs from 'node:fs';",
23
+ "import * as path from 'node:path';",
24
+ "import rulesFile from './rules.json';",
25
+ '',
26
+ 'interface ForbiddenImportCheck {',
27
+ " type: 'forbidden-import';",
28
+ ' // Glob patterns (relative to repo root) selecting the files this rule inspects.',
29
+ ' from: string[];',
30
+ ' // Glob patterns (relative to repo root) that "from" files may not import from.',
31
+ ' forbid: string[];',
32
+ '}',
33
+ '',
34
+ 'interface Rule {',
35
+ ' id: string;',
36
+ ' description: string;',
37
+ ' enabled: boolean;',
38
+ " severity: 'low' | 'medium' | 'high' | 'critical';",
39
+ ' check: ForbiddenImportCheck;',
40
+ '}',
41
+ '',
42
+ 'interface Violation {',
43
+ ' ruleId: string;',
44
+ ' severity: string;',
45
+ ' message: string;',
46
+ '}',
47
+ '',
48
+ "const REPO_ROOT = path.resolve(__dirname, '..');",
49
+ "const IGNORED_DIRS = new Set(['node_modules', '.git', '.swamp', 'cdk.out', 'lib', 'build', 'dist']);",
50
+ '',
51
+ '// Minimal glob -> RegExp: supports "*", "**", "**/" and literal path segments.',
52
+ '// No new dependency for this — the only pattern shapes rules.json ever needs',
53
+ '// are directory-prefix globs like "src/domains/*/commands/**".',
54
+ 'function globToRegExp(glob: string): RegExp {',
55
+ " let out = '';",
56
+ ' for (let i = 0; i < glob.length; i++) {',
57
+ ' const c = glob[i];',
58
+ " if (c === '*' && glob[i + 1] === '*') {",
59
+ ' i++;',
60
+ " if (glob[i + 1] === '/') {",
61
+ " out += '(?:.*/)?';",
62
+ ' i++;',
63
+ ' } else {',
64
+ " out += '.*';",
65
+ ' }',
66
+ " } else if (c === '*') {",
67
+ " out += '[^/]*';",
68
+ " } else if (c === '?') {",
69
+ " out += '[^/]';",
70
+ " } else if ('.+^${}()|[]\\\\'.includes(c)) {",
71
+ ' out += `\\\\${c}`;',
72
+ ' } else {',
73
+ ' out += c;',
74
+ ' }',
75
+ ' }',
76
+ ' return new RegExp(`^${out}$`);',
77
+ '}',
78
+ '',
79
+ 'function matchesAny(relFile: string, globs: string[]): boolean {',
80
+ ' return globs.some((glob) => globToRegExp(glob).test(relFile));',
81
+ '}',
82
+ '',
83
+ 'function toRepoRelative(absPath: string): string {',
84
+ " return path.relative(REPO_ROOT, absPath).split(path.sep).join('/');",
85
+ '}',
86
+ '',
87
+ 'function walk(dir: string, acc: string[] = []): string[] {',
88
+ ' for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {',
89
+ ' if (IGNORED_DIRS.has(entry.name)) continue;',
90
+ ' const full = path.join(dir, entry.name);',
91
+ ' if (entry.isDirectory()) {',
92
+ ' walk(full, acc);',
93
+ " } else if (entry.isFile() && (entry.name.endsWith('.ts') || entry.name.endsWith('.tsx'))) {",
94
+ ' acc.push(full);',
95
+ ' }',
96
+ ' }',
97
+ ' return acc;',
98
+ '}',
99
+ '',
100
+ "const RELATIVE_IMPORT_RE = /(?:import|export)\\s+(?:[^'\";]*?\\bfrom\\s+)?['\"](\\.[^'\"]+)['\"]/g;",
101
+ '',
102
+ 'function findRelativeImports(source: string): string[] {',
103
+ ' const specs: string[] = [];',
104
+ ' RELATIVE_IMPORT_RE.lastIndex = 0;',
105
+ ' let match: RegExpExecArray | null;',
106
+ ' while ((match = RELATIVE_IMPORT_RE.exec(source))) {',
107
+ ' specs.push(match[1]);',
108
+ ' }',
109
+ ' return specs;',
110
+ '}',
111
+ '',
112
+ '// An import spec resolves to a module, not a specific file extension, so a',
113
+ '// forbid glob is checked against the bare resolved path plus its .ts/index.ts',
114
+ '// variants.',
115
+ 'function resolvedCandidates(fromFile: string, spec: string): string[] {',
116
+ ' const resolved = toRepoRelative(path.normalize(path.join(path.dirname(fromFile), spec)));',
117
+ ' return [resolved, `${resolved}.ts`, `${resolved}/index.ts`];',
118
+ '}',
119
+ '',
120
+ 'function evaluateForbiddenImport(rule: Rule): Violation[] {',
121
+ ' const { from, forbid } = rule.check;',
122
+ ' if (from.length === 0 || forbid.length === 0) return [];',
123
+ '',
124
+ ' const violations: Violation[] = [];',
125
+ ' for (const file of walk(REPO_ROOT)) {',
126
+ ' const relFile = toRepoRelative(file);',
127
+ ' if (!matchesAny(relFile, from)) continue;',
128
+ '',
129
+ " const source = fs.readFileSync(file, 'utf8');",
130
+ ' for (const spec of findRelativeImports(source)) {',
131
+ ' const candidates = resolvedCandidates(file, spec);',
132
+ ' if (candidates.some((candidate) => matchesAny(candidate, forbid))) {',
133
+ ' violations.push({',
134
+ ' ruleId: rule.id,',
135
+ ' severity: rule.severity,',
136
+ ' message: `${relFile} imports "${spec}", which resolves under a path forbidden by rule "${rule.id}"`,',
137
+ ' });',
138
+ ' }',
139
+ ' }',
140
+ ' }',
141
+ ' return violations;',
142
+ '}',
143
+ '',
144
+ 'function evaluateRule(rule: Rule): Violation[] {',
145
+ ' switch (rule.check.type) {',
146
+ " case 'forbidden-import':",
147
+ ' return evaluateForbiddenImport(rule);',
148
+ ' default:',
149
+ ' throw new Error(`architecture-rules: unknown check.type "${(rule.check as { type: string }).type}" on rule "${rule.id}"`);',
150
+ ' }',
151
+ '}',
152
+ '',
153
+ 'function main(): number {',
154
+ ' const rules = (rulesFile as { rules: Rule[] }).rules;',
155
+ ' let evaluated = 0;',
156
+ ' let deferred = 0;',
157
+ ' const violations: Violation[] = [];',
158
+ '',
159
+ ' for (const rule of rules) {',
160
+ ' if (!rule.enabled) {',
161
+ ' deferred++;',
162
+ ' console.log(`[deferred] ${rule.id}: ${rule.description}`);',
163
+ ' continue;',
164
+ ' }',
165
+ ' evaluated++;',
166
+ ' violations.push(...evaluateRule(rule));',
167
+ ' }',
168
+ '',
169
+ ' console.log(`\\narchitecture-rules: ${evaluated} rule(s) evaluated, ${deferred} deferred, ${violations.length} violation(s)`);',
170
+ ' for (const violation of violations) {',
171
+ ' console.error(` [${violation.severity}] ${violation.ruleId}: ${violation.message}`);',
172
+ ' }',
173
+ '',
174
+ ' return violations.length > 0 ? 1 : 0;',
175
+ '}',
176
+ '',
177
+ 'process.exit(main());',
178
+ ],
179
+ });
180
+ new projen_1.JsonFile(project, 'architecture-rules/tsconfig.json', {
181
+ marker: false,
182
+ obj: {
183
+ extends: '../tsconfig.json',
184
+ compilerOptions: {
185
+ noEmit: true,
186
+ rootDir: '.',
187
+ },
188
+ include: ['*.ts', '*.json'],
189
+ exclude: ['node_modules'],
190
+ },
191
+ });
192
+ new projen_1.TextFile(project, 'architecture-rules/README.md', {
193
+ marker: true,
194
+ lines: [
195
+ '# Architecture rules',
196
+ '',
197
+ 'A data-driven registry of architectural constraints, enforced by',
198
+ '`pnpm validate:architecture` and run as part of the pre-PR verification',
199
+ 'workflow (`workflows/workflow-pre-pr-verify.yaml`) before the swamp factory',
200
+ 'opens a PR. New rules are new entries in `rules.json`, not new code — the',
201
+ 'engine (`check.ts`) only needs a new case in `evaluateRule()` when a rule',
202
+ 'needs a genuinely new kind of check.',
203
+ '',
204
+ '## Rule shape',
205
+ '',
206
+ '```json',
207
+ '{',
208
+ ' "id": "unique-id",',
209
+ ' "description": "What this enforces and why.",',
210
+ ' "enabled": true,',
211
+ ' "severity": "low" | "medium" | "high" | "critical",',
212
+ ' "check": {',
213
+ ' "type": "forbidden-import",',
214
+ ' "from": ["glob patterns selecting files this rule inspects"],',
215
+ ' "forbid": ["glob patterns those files may not import from"]',
216
+ ' }',
217
+ '}',
218
+ '```',
219
+ '',
220
+ '`enabled: false` rules are skipped (reported as "deferred") rather than',
221
+ "evaluated — use this for a rule you've named and described but don't yet",
222
+ 'have a concrete convention to check.',
223
+ '',
224
+ '## Current rules',
225
+ '',
226
+ 'None yet — `rules.json` ships empty. Add rules as your domain module',
227
+ 'conventions solidify (e.g. an event-sourced/CQRS module layout under',
228
+ '`src/domains/<name>/{commands,events,aggregates,projections}/`).',
229
+ '',
230
+ '## Adding a check type',
231
+ '',
232
+ '`forbidden-import` is the only implemented `check.type`. If a future rule',
233
+ 'needs something it can\'t express (e.g. "every aggregate file must have a',
234
+ 'matching events file"), add a new `type` value, a matching interface, and a',
235
+ 'case in `evaluateRule()` in `check.ts` — keep the engine itself generic;',
236
+ 'rule-specific knowledge belongs in `rules.json`.',
237
+ ],
238
+ });
239
+ new projen_1.SampleFile(project, 'architecture-rules/rules.json', {
240
+ contents: JSON.stringify({ rules: [] }, null, 2) + '\n',
241
+ });
242
+ }
243
+ }
244
+ exports.SoftwareFactoryArchitectureRules = SoftwareFactoryArchitectureRules;
245
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiU29mdHdhcmVGYWN0b3J5QXJjaGl0ZWN0dXJlUnVsZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvU29mdHdhcmVGYWN0b3J5QXJjaGl0ZWN0dXJlUnVsZXMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBQUEsbUNBQXdEO0FBR3hEOzs7OztHQUtHO0FBQ0gsTUFBYSxnQ0FBZ0M7SUFDM0MsWUFBWSxPQUFvQjtRQUM5QixJQUFJLGlCQUFRLENBQUMsT0FBTyxFQUFFLDZCQUE2QixFQUFFO1lBQ25ELE1BQU0sRUFBRSxJQUFJO1lBQ1osS0FBSyxFQUFFO2dCQUNMLEtBQUs7Z0JBQ0wsK0VBQStFO2dCQUMvRSw4RUFBOEU7Z0JBQzlFLGdGQUFnRjtnQkFDaEYsOEJBQThCO2dCQUM5QixLQUFLO2dCQUNMLGdDQUFnQztnQkFDaEMsb0NBQW9DO2dCQUNwQyx1Q0FBdUM7Z0JBQ3ZDLEVBQUU7Z0JBQ0Ysa0NBQWtDO2dCQUNsQyw2QkFBNkI7Z0JBQzdCLG9GQUFvRjtnQkFDcEYsbUJBQW1CO2dCQUNuQixtRkFBbUY7Z0JBQ25GLHFCQUFxQjtnQkFDckIsR0FBRztnQkFDSCxFQUFFO2dCQUNGLGtCQUFrQjtnQkFDbEIsZUFBZTtnQkFDZix3QkFBd0I7Z0JBQ3hCLHFCQUFxQjtnQkFDckIscURBQXFEO2dCQUNyRCxnQ0FBZ0M7Z0JBQ2hDLEdBQUc7Z0JBQ0gsRUFBRTtnQkFDRix1QkFBdUI7Z0JBQ3ZCLG1CQUFtQjtnQkFDbkIscUJBQXFCO2dCQUNyQixvQkFBb0I7Z0JBQ3BCLEdBQUc7Z0JBQ0gsRUFBRTtnQkFDRixrREFBa0Q7Z0JBQ2xELHNHQUFzRztnQkFDdEcsRUFBRTtnQkFDRixpRkFBaUY7Z0JBQ2pGLCtFQUErRTtnQkFDL0UsaUVBQWlFO2dCQUNqRSwrQ0FBK0M7Z0JBQy9DLGlCQUFpQjtnQkFDakIsMkNBQTJDO2dCQUMzQyx3QkFBd0I7Z0JBQ3hCLDZDQUE2QztnQkFDN0MsWUFBWTtnQkFDWixrQ0FBa0M7Z0JBQ2xDLDRC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QUMsT0FBTyxFQUFFLDhCQUE4QixFQUFFO1lBQ3BELE1BQU0sRUFBRSxJQUFJO1lBQ1osS0FBSyxFQUFFO2dCQUNMLHNCQUFzQjtnQkFDdEIsRUFBRTtnQkFDRixrRUFBa0U7Z0JBQ2xFLHlFQUF5RTtnQkFDekUsNkVBQTZFO2dCQUM3RSwyRUFBMkU7Z0JBQzNFLDJFQUEyRTtnQkFDM0Usc0NBQXNDO2dCQUN0QyxFQUFFO2dCQUNGLGVBQWU7Z0JBQ2YsRUFBRTtnQkFDRixTQUFTO2dCQUNULEdBQUc7Z0JBQ0gsc0JBQXNCO2dCQUN0QixpREFBaUQ7Z0JBQ2pELG9CQUFvQjtnQkFDcEIsdURBQXVEO2dCQUN2RCxjQUFjO2dCQUNkLGlDQUFpQztnQkFDakMsbUVBQW1FO2dCQUNuRSxpRUFBaUU7Z0JBQ2pFLEtBQUs7Z0JBQ0wsR0FBRztnQkFDSCxLQUFLO2dCQUNMLEVBQUU7Z0JBQ0YseUVBQXlFO2dCQUN6RSwwRUFBMEU7Z0JBQzFFLHNDQUFzQztnQkFDdEMsRUFBRTtnQkFDRixrQkFBa0I7Z0JBQ2xCLEVBQUU7Z0JBQ0Ysc0VBQXNFO2dCQUN0RSxzRUFBc0U7Z0JBQ3RFLGtFQUFrRTtnQkFDbEUsRUFBRTtnQkFDRix3QkFBd0I7Z0JBQ3hCLEVBQUU7Z0JBQ0YsMkVBQTJFO2dCQUMzRSwyRUFBMkU7Z0JBQzNFLDZFQUE2RTtnQkFDN0UsMEVBQTBFO2dCQUMxRSxrREFBa0Q7YUFDbkQ7U0FDRixDQUFDLENBQUM7UUFFSCxJQUFJLG1CQUFVLENBQUMsT0FBTyxFQUFFLCtCQUErQixFQUFFO1lBQ3ZELFFBQVEsRUFBRSxJQUFJLENBQUMsU0FBUyxDQUFDLEVBQUUsS0FBSyxFQUFFLEVBQUUsRUFBRSxFQUFFLElBQUksRUFBRSxDQUFDLENBQUMsR0FBRyxJQUFJO1NBQ3hELENBQUMsQ0FBQztJQUNMLENBQUM7Q0FDRjtBQTNPRCw0RUEyT0MiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBKc29uRmlsZSwgU2FtcGxlRmlsZSwgVGV4dEZpbGUgfSBmcm9tICdwcm9qZW4nO1xuaW1wb3J0IHsgTm9kZVByb2plY3QgfSBmcm9tICdwcm9qZW4vbGliL2phdmFzY3JpcHQnO1xuXG4vKipcbiAqIFRoZSBnZW5lcmljLCBkYXRhLWRyaXZlbiBhcmNoaXRlY3R1cmUgcnVsZSBlbmdpbmUgcGx1cyBhbiBlbXB0eSBzZWVkXG4gKiBydWxlcy5qc29uLiBgY2hlY2sudHNgL2B0c2NvbmZpZy5qc29uYC9gUkVBRE1FLm1kYCBhcmUgbWFuYWdlZCAoQmF0bWFuXG4gKiBvd25zIHRoZSBlbmdpbmUpOyBgcnVsZXMuanNvbmAgaXMgYSBgU2FtcGxlRmlsZWAg4oCUIGVhY2ggcmVwbydzIG93biBydWxlc1xuICogYXJlIGRhdGEgdGhlIHRlYW0gb3ducywgbm90IHNvbWV0aGluZyBCYXRtYW4gc2hvdWxkIG92ZXJ3cml0ZS5cbiAqL1xuZXhwb3J0IGNsYXNzIFNvZnR3YXJlRmFjdG9yeUFyY2hpdGVjdHVyZVJ1bGVzIHtcbiAgY29uc3RydWN0b3IocHJvamVjdDogTm9kZVByb2plY3QpIHtcbiAgICBuZXcgVGV4dEZpbGUocHJvamVjdCwgJ2FyY2hpdGVjdHVyZS1ydWxlcy9jaGVjay50cycsIHtcbiAgICAgIG1hcmtlcjogdHJ1ZSxcbiAgICAgIGxpbmVzOiBbXG4gICAgICAgICcvKionLFxuICAgICAgICAnICogR2VuZXJpYyBhcmNoaXRlY3R1cmUtcnVsZSBlbmdpbmUuIFJ1bGVzIGxpdmUgZW50aXJlbHkgaW4gcnVsZXMuanNvbiDigJQgdGhpcycsXG4gICAgICAgICcgKiBmaWxlIG9ubHkga25vd3MgaG93IHRvIGV2YWx1YXRlIHRoZSBjaGVjayBcInR5cGVzXCIgdGhhdCBydWxlcyBjYW4gZGVjbGFyZS4nLFxuICAgICAgICAnICogQWRkIGEgbmV3IHJ1bGUgYnkgYWRkaW5nIGEgcnVsZXMuanNvbiBlbnRyeTsgYWRkIGEgbmV3IGNoZWNrIHR5cGUgYnkgYWRkaW5nJyxcbiAgICAgICAgJyAqIGEgY2FzZSBpbiBldmFsdWF0ZVJ1bGUoKS4nLFxuICAgICAgICAnICovJyxcbiAgICAgICAgXCJpbXBvcnQgKiBhcyBmcyBmcm9tICdub2RlOmZzJztcIixcbiAgICAgICAgXCJpbXBvcnQgKiBhcyBwYXRoIGZyb20gJ25vZGU6cGF0aCc7XCIsXG4gICAgICAgIFwiaW1wb3J0IHJ1bGVzRmlsZSBmcm9tICcuL3J1bGVzLmpzb24nO1wiLFxuICAgICAgICAnJyxcbiAgICAgICAgJ2ludGVyZmFjZSBGb3JiaWRkZW5JbXBvcnRDaGVjayB7JyxcbiAgICAgICAgXCIgIHR5cGU6ICdmb3JiaWRkZW4taW1wb3J0JztcIixcbiAgICAgICAgJyAgLy8gR2xvYiBwYXR0ZXJucyAocmVsYXRpdmUgdG8gcmVwbyByb290KSBzZWxlY3RpbmcgdGhlIGZpbGVzIHRoaXMgcnVsZSBpbnNwZWN0cy4nLFxuICAgICAgICAnICBmcm9tOiBzdHJpbmdbXTsnLFxuICAgICAgICAnICAvLyBHbG9iIHBhdHRlcm5zIChyZWxhdGl2ZSB0byByZXBvIHJvb3QpIHRoYXQgXCJmcm9tXCIgZmlsZXMgbWF5IG5vdCBpbXBvcnQgZnJvbS4nLFxuICAgICAgICAnICBmb3JiaWQ6IHN0cmluZ1tdOycsXG4gICAgICAgICd9JyxcbiAgICAgICAgJycsXG4gICAgICAgICdpbnRlcmZhY2UgUnVsZSB7JyxcbiAgICAgICAgJyAgaWQ6IHN0cmluZzsnLFxuICAgICAgICAnICBkZXNjcmlwdGlvbjogc3RyaW5nOycsXG4gICAgICAgICcgIGVuYWJsZWQ6IGJvb2xlYW47JyxcbiAgICAgICAgXCIgIHNldmVyaXR5OiAnbG93JyB8ICdtZWRpdW0nIHwgJ2hpZ2gnIHwgJ2NyaXRpY2FsJztcIixcbiAgICAgICAgJyAgY2hlY2s6IEZvcmJpZGRlbkltcG9ydENoZWNrOycsXG4gICAgICAgICd9JyxcbiAgICAgICAgJycsXG4gICAgICAgICdpbnRlcmZhY2UgVmlvbGF0aW9uIHsnLFxuICAgICAgICAnICBydWxlSWQ6IHN0cmluZzsnLFxuICAgICAgICAnICBzZXZlcml0eTogc3RyaW5nOycsXG4gICAgICAgICcgIG1lc3NhZ2U6IHN0cmluZzsnLFxuICAgICAgICAnfScsXG4gICAgICAgICcnLFxuICAgICAgICBcImNvbnN0IFJFUE9fUk9PVCA9IHBhdGgucmVzb2x2ZShfX2Rpcm5hbWUsICcuLicpO1wiLFxuICAgICAgICBcImNvbnN0IElHTk9SRURfRElSUyA9IG5ldyBTZXQoWydub2RlX21vZHVsZXMnLCAnLmdpdCcsICcuc3dhbXAnLCAnY2RrLm91dCcsICdsaWInLCAnYnVpbGQnLCAnZGlzdCddKTtcIixcbiAgICAgICAgJycsXG4gICAgICAgICcvLyBNaW5pbWFsIGdsb2IgLT4gUmVnRXhwOiBzdXBwb3J0cyBcIipcIiwgXCIqKlwiLCBcIioqL1wiIGFuZCBsaXRlcmFsIHBhdGggc2VnbWVudHMuJyxcbiAgICAgICAgJy8vIE5vIG5ldyBkZXBlbmRlbmN5IGZvciB0aGlzIOKAlCB0aGUgb25seSBwYXR0ZXJuIHNoYXBlcyBydWxlcy5qc29uIGV2ZXIgbmVlZHMnLFxuICAgICAgICAnLy8gYXJlIGRpcmVjdG9yeS1wcmVmaXggZ2xvYnMgbGlrZSBcInNyYy9kb21haW5zLyovY29tbWFuZHMvKipcIi4nLFxuICAgICAgICAnZnVuY3Rpb24gZ2xvYlRvUmVnRXhwKGdsb2I6IHN0cmluZyk6IFJlZ0V4cCB7JyxcbiAgICAgICAgXCIgIGxldCBvdXQgPSAnJztcIixcbiAgICAgICAgJyAgZm9yIChsZXQgaSA9IDA7IGkgPCBnbG9iLmxlbmd0aDsgaSsrKSB7JyxcbiAgICAgICAgJyAgICBjb25zdCBjID0gZ2xvYltpXTsnLFxuICAgICAgICBcIiAgICBpZiAoYyA9PT0gJyonICYmIGdsb2JbaSArIDFdID09PSAnKicpIHtcIixcbiAgICAgICAgJyAgICAgIGkrKzsnLFxuICAgICAgICBcIiAgICAgIGlmIChnbG9iW2kgKyAxXSA9PT0gJy8nKSB7XCIsXG4gICAgICAgIFwiICAgICAgICBvdXQgKz0gJyg/Oi4qLyk/JztcIixcbiAgICAgICAgJyAgICAgICAgaSsrOycsXG4gICAgICAgICcgICAgICB9IGVsc2UgeycsXG4gICAgICAgIFwiICAgICAgICBvdXQgKz0gJy4qJztcIixcbiAgICAgICAgJyAgICAgIH0nLFxuICAgICAgICBcIiAgICB9IGVsc2UgaWYgKGMgPT09ICcqJykge1wiLFxuICAgICAgICBcIiAgICAgIG91dCArPSAnW14vXSonO1wiLFxuICAgICAgICBcIiAgICB9IGVsc2UgaWYgKGMgPT09ICc/Jykge1wiLFxuICAgICAgICBcIiAgICAgIG91dCArPSAnW14vXSc7XCIsXG4gICAgICAgIFwiICAgIH0gZWxzZSBpZiAoJy4rXiR7fSgpfFtdXFxcXFxcXFwnLmluY2x1ZGVzKGMpKSB7XCIsXG4gICAgICAgICcgICAgICBvdXQgKz0gYFxcXFxcXFxcJHtjfWA7JyxcbiAgICAgICAgJyAgICB9IGVsc2UgeycsXG4gICAgICAgICcgICAgICBvdXQgKz0gYzsnLFxuICAgICAgICAnICAgIH0nLFxuICAgICAgICAnICB9JyxcbiAgICAgICAgJyAgcmV0dXJuIG5ldyBSZWdFeHAoYF4ke291dH0kYCk7JyxcbiAgICAgICAgJ30nLFxuICAgICAgICAnJyxcbiAgICAgICAgJ2Z1bmN0aW9uIG1hdGNoZXNBbnkocmVsRmlsZTogc3RyaW5nLCBnbG9iczogc3RyaW5nW10pOiBib29sZWFuIHsnLFxuICAgICAgICAnICByZXR1cm4gZ2xvYnMuc29tZSgoZ2xvYikgPT4gZ2xvYlRvUmVnRXhwKGdsb2IpLnRlc3QocmVsRmlsZSkpOycsXG4gICAgICAgICd9JyxcbiAgICAgICAgJycsXG4gICAgICAgICdmdW5jdGlvbiB0b1JlcG9SZWxhdGl2ZShhYnNQYXRoOiBzdHJpbmcpOiBzdHJpbmcgeycsXG4gICAgICAgIFwiICByZXR1cm4gcGF0aC5yZWxhdGl2ZShSRVBPX1JPT1QsIGFic1BhdGgpLnNwbGl0KHBhdGguc2VwKS5qb2luKCcvJyk7XCIsXG4gICAgICAgICd9JyxcbiAgICAgICAgJycsXG4gICAgICAgICdmdW5jdGlvbiB3YWxrKGRpcjogc3RyaW5nLCBhY2M6IHN0cmluZ1tdID0gW10pOiBzdHJpbmdbXSB7JyxcbiAgICAgICAgJyAgZm9yIChjb25zdCBlbnRyeSBvZiBmcy5yZWFkZGlyU3luYyhkaXIsIHsgd2l0aEZpbGVUeXBlczogdHJ1ZSB9KSkgeycsXG4gICAgICAgICcgICAgaWYgKElHTk9SRURfRElSUy5oYXMoZW50cnkubmFtZSkpIGNvbnRpbnVlOycsXG4gICAgICAgICcgICAgY29uc3QgZnVsbCA9IHBhdGguam9pbihkaXIsIGVudHJ5Lm5hbWUpOycsXG4gICAgICAgICcgICAgaWYgKGVudHJ5LmlzRGlyZWN0b3J5KCkpIHsnLFxuICAgICAgICAnICAgICAgd2FsayhmdWxsLCBhY2MpOycsXG4gICAgICAgIFwiICAgIH0gZWxzZSBpZiAoZW50cnkuaXNGaWxlKCkgJiYgKGVudHJ5Lm5hbWUuZW5kc1dpdGgoJy50cycpIHx8IGVudHJ5Lm5hbWUuZW5kc1dpdGgoJy50c3gnKSkpIHtcIixcbiAgICAgICAgJyAgICAgIGFjYy5wdXNoKGZ1bGwpOycsXG4gICAgICAgICcgICAgfScsXG4gICAgICAgICcgIH0nLFxuICAgICAgICAnICByZXR1cm4gYWNjOycsXG4gICAgICAgICd9JyxcbiAgICAgICAgJycsXG4gICAgICAgIFwiY29uc3QgUkVMQVRJVkVfSU1QT1JUX1JFID0gLyg/OmltcG9ydHxleHBvcnQpXFxcXHMrKD86W14nXFxcIjtdKj9cXFxcYmZyb21cXFxccyspP1snXFxcIl0oXFxcXC5bXidcXFwiXSspWydcXFwiXS9nO1wiLFxuICAgICAgICAnJyxcbiAgICAgICAgJ2Z1bmN0aW9uIGZpbmRSZWxhdGl2ZUltcG9ydHMoc291cmNlOiBzdHJpbmcpOiBzdHJpbmdbXSB7JyxcbiAgICAgICAgJyAgY29uc3Qgc3BlY3M6IHN0cmluZ1tdID0gW107JyxcbiAgICAgICAgJyAgUkVMQVRJVkVfSU1QT1JUX1JFLmxhc3RJbmRleCA9IDA7JyxcbiAgICAgICAgJyAgbGV0IG1hdGNoOiBSZWdFeHBFeGVjQXJyYXkgfCBudWxsOycsXG4gICAgICAgICcgIHdoaWxlICgobWF0Y2ggPSBSRUxBVElWRV9JTVBPUlRfUkUuZXhlYyhzb3VyY2UpKSkgeycsXG4gICAgICAgICcgICAgc3BlY3MucHVzaChtYXRjaFsxXSk7JyxcbiAgICAgICAgJyAgfScsXG4gICAgICAgICcgIHJldHVybiBzcGVjczsnLFxuICAgICAgICAnfScsXG4gICAgICAgICcnLFxuICAgICAgICAnLy8gQW4gaW1wb3J0IHNwZWMgcmVzb2x2ZXMgdG8gYSBtb2R1bGUsIG5vdCBhIHNwZWNpZmljIGZpbGUgZXh0ZW5zaW9uLCBzbyBhJyxcbiAgICAgICAgJy8vIGZvcmJpZCBnbG9iIGlzIGNoZWNrZWQgYWdhaW5zdCB0aGUgYmFyZSByZXNvbHZlZCBwYXRoIHBsdXMgaXRzIC50cy9pbmRleC50cycsXG4gICAgICAgICcvLyB2YXJpYW50cy4nLFxuICAgICAgICAnZnVuY3Rpb24gcmVzb2x2ZWRDYW5kaWRhdGVzKGZyb21GaWxlOiBzdHJpbmcsIHNwZWM6IHN0cmluZyk6IHN0cmluZ1tdIHsnLFxuICAgICAgICAnICBjb25zdCByZXNvbHZlZCA9IHRvUmVwb1JlbGF0aXZlKHBhdGgubm9ybWFsaXplKHBhdGguam9pbihwYXRoLmRpcm5hbWUoZnJvbUZpbGUpLCBzcGVjKSkpOycsXG4gICAgICAgICcgIHJldHVybiBbcmVzb2x2ZWQsIGAke3Jlc29sdmVkfS50c2AsIGAke3Jlc29sdmVkfS9pbmRleC50c2BdOycsXG4gICAgICAgICd9JyxcbiAgICAgICAgJycsXG4gICAgICAgICdmdW5jdGlvbiBldmFsdWF0ZUZvcmJpZGRlbkltcG9ydChydWxlOiBSdWxlKTogVmlvbGF0aW9uW10geycsXG4gICAgICAgICcgIGNvbnN0IHsgZnJvbSwgZm9yYmlkIH0gPSBydWxlLmNoZWNrOycsXG4gICAgICAgICcgIGlmIChmcm9tLmxlbmd0aCA9PT0gMCB8fCBmb3JiaWQubGVuZ3RoID09PSAwKSByZXR1cm4gW107JyxcbiAgICAgICAgJycsXG4gICAgICAgICcgIGNvbnN0IHZpb2xhdGlvbnM6IFZpb2xhdGlvbltdID0gW107JyxcbiAgICAgICAgJyAgZm9yIChjb25zdCBmaWxlIG9mIHdhbGsoUkVQT19ST09UKSkgeycsXG4gICAgICAgICcgICAgY29uc3QgcmVsRmlsZSA9IHRvUmVwb1JlbGF0aXZlKGZpbGUpOycsXG4gICAgICAgICcgICAgaWYgKCFtYXRjaGVzQW55KHJlbEZpbGUsIGZyb20pKSBjb250aW51ZTsnLFxuICAgICAgICAnJyxcbiAgICAgICAgXCIgICAgY29uc3Qgc291cmNlID0gZnMucmVhZEZpbGVTeW5jKGZpbGUsICd1dGY4Jyk7XCIsXG4gICAgICAgICcgICAgZm9yIChjb25zdCBzcGVjIG9mIGZpbmRSZWxhdGl2ZUltcG9ydHMoc291cmNlKSkgeycsXG4gICAgICAgICcgICAgICBjb25zdCBjYW5kaWRhdGVzID0gcmVzb2x2ZWRDYW5kaWRhdGVzKGZpbGUsIHNwZWMpOycsXG4gICAgICAgICcgICAgICBpZiAoY2FuZGlkYXRlcy5zb21lKChjYW5kaWRhdGUpID0+IG1hdGNoZXNBbnkoY2FuZGlkYXRlLCBmb3JiaWQpKSkgeycsXG4gICAgICAgICcgICAgICAgIHZpb2xhdGlvbnMucHVzaCh7JyxcbiAgICAgICAgJyAgICAgICAgICBydWxlSWQ6IHJ1bGUuaWQsJyxcbiAgICAgICAgJyAgICAgICAgICBzZXZlcml0eTogcnVsZS5zZXZlcml0eSwnLFxuICAgICAgICAnICAgICAgICAgIG1lc3NhZ2U6IGAke3JlbEZpbGV9IGltcG9ydHMgXCIke3NwZWN9XCIsIHdoaWNoIHJlc29sdmVzIHVuZGVyIGEgcGF0aCBmb3JiaWRkZW4gYnkgcnVsZSBcIiR7cnVsZS5pZH1cImAsJyxcbiAgICAgICAgJyAgICAgICAgfSk7JyxcbiAgICAgICAgJyAgICAgIH0nLFxuICAgICAgICAnICAgIH0nLFxuICAgICAgICAnICB9JyxcbiAgICAgICAgJyAgcmV0dXJuIHZpb2xhdGlvbnM7JyxcbiAgICAgICAgJ30nLFxuICAgICAgICAnJyxcbiAgICAgICAgJ2Z1bmN0aW9uIGV2YWx1YXRlUnVsZShydWxlOiBSdWxlKTogVmlvbGF0aW9uW10geycsXG4gICAgICAgICcgIHN3aXRjaCAocnVsZS5jaGVjay50eXBlKSB7JyxcbiAgICAgICAgXCIgICAgY2FzZSAnZm9yYmlkZGVuLWltcG9ydCc6XCIsXG4gICAgICAgICcgICAgICByZXR1cm4gZXZhbHVhdGVGb3JiaWRkZW5JbXBvcnQocnVsZSk7JyxcbiAgICAgICAgJyAgICBkZWZhdWx0OicsXG4gICAgICAgICcgICAgICB0aHJvdyBuZXcgRXJyb3IoYGFyY2hpdGVjdHVyZS1ydWxlczogdW5rbm93biBjaGVjay50eXBlIFwiJHsocnVsZS5jaGVjayBhcyB7IHR5cGU6IHN0cmluZyB9KS50eXBlfVwiIG9uIHJ1bGUgXCIke3J1bGUuaWR9XCJgKTsnLFxuICAgICAgICAnICB9JyxcbiAgICAgICAgJ30nLFxuICAgICAgICAnJyxcbiAgICAgICAgJ2Z1bmN0aW9uIG1haW4oKTogbnVtYmVyIHsnLFxuICAgICAgICAnICBjb25zdCBydWxlcyA9IChydWxlc0ZpbGUgYXMgeyBydWxlczogUnVsZVtdIH0pLnJ1bGVzOycsXG4gICAgICAgICcgIGxldCBldmFsdWF0ZWQgPSAwOycsXG4gICAgICAgICcgIGxldCBkZWZlcnJlZCA9IDA7JyxcbiAgICAgICAgJyAgY29uc3QgdmlvbGF0aW9uczogVmlvbGF0aW9uW10gPSBbXTsnLFxuICAgICAgICAnJyxcbiAgICAgICAgJyAgZm9yIChjb25zdCBydWxlIG9mIHJ1bGVzKSB7JyxcbiAgICAgICAgJyAgICBpZiAoIXJ1bGUuZW5hYmxlZCkgeycsXG4gICAgICAgICcgICAgICBkZWZlcnJlZCsrOycsXG4gICAgICAgICcgICAgICBjb25zb2xlLmxvZyhgW2RlZmVycmVkXSAke3J1bGUuaWR9OiAke3J1bGUuZGVzY3JpcHRpb259YCk7JyxcbiAgICAgICAgJyAgICAgIGNvbnRpbnVlOycsXG4gICAgICAgICcgICAgfScsXG4gICAgICAgICcgICAgZXZhbHVhdGVkKys7JyxcbiAgICAgICAgJyAgICB2aW9sYXRpb25zLnB1c2goLi4uZXZhbHVhdGVSdWxlKHJ1bGUpKTsnLFxuICAgICAgICAnICB9JyxcbiAgICAgICAgJycsXG4gICAgICAgICcgIGNvbnNvbGUubG9nKGBcXFxcbmFyY2hpdGVjdHVyZS1ydWxlczogJHtldmFsdWF0ZWR9IHJ1bGUocykgZXZhbHVhdGVkLCAke2RlZmVycmVkfSBkZWZlcnJlZCwgJHt2aW9sYXRpb25zLmxlbmd0aH0gdmlvbGF0aW9uKHMpYCk7JyxcbiAgICAgICAgJyAgZm9yIChjb25zdCB2aW9sYXRpb24gb2YgdmlvbGF0aW9ucykgeycsXG4gICAgICAgICcgICAgY29uc29sZS5lcnJvcihgICBbJHt2aW9sYXRpb24uc2V2ZXJpdHl9XSAke3Zpb2xhdGlvbi5ydWxlSWR9OiAke3Zpb2xhdGlvbi5tZXNzYWdlfWApOycsXG4gICAgICAgICcgIH0nLFxuICAgICAgICAnJyxcbiAgICAgICAgJyAgcmV0dXJuIHZpb2xhdGlvbnMubGVuZ3RoID4gMCA/IDEgOiAwOycsXG4gICAgICAgICd9JyxcbiAgICAgICAgJycsXG4gICAgICAgICdwcm9jZXNzLmV4aXQobWFpbigpKTsnLFxuICAgICAgXSxcbiAgICB9KTtcblxuICAgIG5ldyBKc29uRmlsZShwcm9qZWN0LCAnYXJjaGl0ZWN0dXJlLXJ1bGVzL3RzY29uZmlnLmpzb24nLCB7XG4gICAgICBtYXJrZXI6IGZhbHNlLFxuICAgICAgb2JqOiB7XG4gICAgICAgIGV4dGVuZHM6ICcuLi90c2NvbmZpZy5qc29uJyxcbiAgICAgICAgY29tcGlsZXJPcHRpb25zOiB7XG4gICAgICAgICAgbm9FbWl0OiB0cnVlLFxuICAgICAgICAgIHJvb3REaXI6ICcuJyxcbiAgICAgICAgfSxcbiAgICAgICAgaW5jbHVkZTogWycqLnRzJywgJyouanNvbiddLFxuICAgICAgICBleGNsdWRlOiBbJ25vZGVfbW9kdWxlcyddLFxuICAgICAgfSxcbiAgICB9KTtcblxuICAgIG5ldyBUZXh0RmlsZShwcm9qZWN0LCAnYXJjaGl0ZWN0dXJlLXJ1bGVzL1JFQURNRS5tZCcsIHtcbiAgICAgIG1hcmtlcjogdHJ1ZSxcbiAgICAgIGxpbmVzOiBbXG4gICAgICAgICcjIEFyY2hpdGVjdHVyZSBydWxlcycsXG4gICAgICAgICcnLFxuICAgICAgICAnQSBkYXRhLWRyaXZlbiByZWdpc3RyeSBvZiBhcmNoaXRlY3R1cmFsIGNvbnN0cmFpbnRzLCBlbmZvcmNlZCBieScsXG4gICAgICAgICdgcG5wbSB2YWxpZGF0ZTphcmNoaXRlY3R1cmVgIGFuZCBydW4gYXMgcGFydCBvZiB0aGUgcHJlLVBSIHZlcmlmaWNhdGlvbicsXG4gICAgICAgICd3b3JrZmxvdyAoYHdvcmtmbG93cy93b3JrZmxvdy1wcmUtcHItdmVyaWZ5LnlhbWxgKSBiZWZvcmUgdGhlIHN3YW1wIGZhY3RvcnknLFxuICAgICAgICAnb3BlbnMgYSBQUi4gTmV3IHJ1bGVzIGFyZSBuZXcgZW50cmllcyBpbiBgcnVsZXMuanNvbmAsIG5vdCBuZXcgY29kZSDigJQgdGhlJyxcbiAgICAgICAgJ2VuZ2luZSAoYGNoZWNrLnRzYCkgb25seSBuZWVkcyBhIG5ldyBjYXNlIGluIGBldmFsdWF0ZVJ1bGUoKWAgd2hlbiBhIHJ1bGUnLFxuICAgICAgICAnbmVlZHMgYSBnZW51aW5lbHkgbmV3IGtpbmQgb2YgY2hlY2suJyxcbiAgICAgICAgJycsXG4gICAgICAgICcjIyBSdWxlIHNoYXBlJyxcbiAgICAgICAgJycsXG4gICAgICAgICdgYGBqc29uJyxcbiAgICAgICAgJ3snLFxuICAgICAgICAnICBcImlkXCI6IFwidW5pcXVlLWlkXCIsJyxcbiAgICAgICAgJyAgXCJkZXNjcmlwdGlvblwiOiBcIldoYXQgdGhpcyBlbmZvcmNlcyBhbmQgd2h5LlwiLCcsXG4gICAgICAgICcgIFwiZW5hYmxlZFwiOiB0cnVlLCcsXG4gICAgICAgICcgIFwic2V2ZXJpdHlcIjogXCJsb3dcIiB8IFwibWVkaXVtXCIgfCBcImhpZ2hcIiB8IFwiY3JpdGljYWxcIiwnLFxuICAgICAgICAnICBcImNoZWNrXCI6IHsnLFxuICAgICAgICAnICAgIFwidHlwZVwiOiBcImZvcmJpZGRlbi1pbXBvcnRcIiwnLFxuICAgICAgICAnICAgIFwiZnJvbVwiOiBbXCJnbG9iIHBhdHRlcm5zIHNlbGVjdGluZyBmaWxlcyB0aGlzIHJ1bGUgaW5zcGVjdHNcIl0sJyxcbiAgICAgICAgJyAgICBcImZvcmJpZFwiOiBbXCJnbG9iIHBhdHRlcm5zIHRob3NlIGZpbGVzIG1heSBub3QgaW1wb3J0IGZyb21cIl0nLFxuICAgICAgICAnICB9JyxcbiAgICAgICAgJ30nLFxuICAgICAgICAnYGBgJyxcbiAgICAgICAgJycsXG4gICAgICAgICdgZW5hYmxlZDogZmFsc2VgIHJ1bGVzIGFyZSBza2lwcGVkIChyZXBvcnRlZCBhcyBcImRlZmVycmVkXCIpIHJhdGhlciB0aGFuJyxcbiAgICAgICAgXCJldmFsdWF0ZWQg4oCUIHVzZSB0aGlzIGZvciBhIHJ1bGUgeW91J3ZlIG5hbWVkIGFuZCBkZXNjcmliZWQgYnV0IGRvbid0IHlldFwiLFxuICAgICAgICAnaGF2ZSBhIGNvbmNyZXRlIGNvbnZlbnRpb24gdG8gY2hlY2suJyxcbiAgICAgICAgJycsXG4gICAgICAgICcjIyBDdXJyZW50IHJ1bGVzJyxcbiAgICAgICAgJycsXG4gICAgICAgICdOb25lIHlldCDigJQgYHJ1bGVzLmpzb25gIHNoaXBzIGVtcHR5LiBBZGQgcnVsZXMgYXMgeW91ciBkb21haW4gbW9kdWxlJyxcbiAgICAgICAgJ2NvbnZlbnRpb25zIHNvbGlkaWZ5IChlLmcuIGFuIGV2ZW50LXNvdXJjZWQvQ1FSUyBtb2R1bGUgbGF5b3V0IHVuZGVyJyxcbiAgICAgICAgJ2BzcmMvZG9tYWlucy88bmFtZT4ve2NvbW1hbmRzLGV2ZW50cyxhZ2dyZWdhdGVzLHByb2plY3Rpb25zfS9gKS4nLFxuICAgICAgICAnJyxcbiAgICAgICAgJyMjIEFkZGluZyBhIGNoZWNrIHR5cGUnLFxuICAgICAgICAnJyxcbiAgICAgICAgJ2Bmb3JiaWRkZW4taW1wb3J0YCBpcyB0aGUgb25seSBpbXBsZW1lbnRlZCBgY2hlY2sudHlwZWAuIElmIGEgZnV0dXJlIHJ1bGUnLFxuICAgICAgICAnbmVlZHMgc29tZXRoaW5nIGl0IGNhblxcJ3QgZXhwcmVzcyAoZS5nLiBcImV2ZXJ5IGFnZ3JlZ2F0ZSBmaWxlIG11c3QgaGF2ZSBhJyxcbiAgICAgICAgJ21hdGNoaW5nIGV2ZW50cyBmaWxlXCIpLCBhZGQgYSBuZXcgYHR5cGVgIHZhbHVlLCBhIG1hdGNoaW5nIGludGVyZmFjZSwgYW5kIGEnLFxuICAgICAgICAnY2FzZSBpbiBgZXZhbHVhdGVSdWxlKClgIGluIGBjaGVjay50c2Ag4oCUIGtlZXAgdGhlIGVuZ2luZSBpdHNlbGYgZ2VuZXJpYzsnLFxuICAgICAgICAncnVsZS1zcGVjaWZpYyBrbm93bGVkZ2UgYmVsb25ncyBpbiBgcnVsZXMuanNvbmAuJyxcbiAgICAgIF0sXG4gICAgfSk7XG5cbiAgICBuZXcgU2FtcGxlRmlsZShwcm9qZWN0LCAnYXJjaGl0ZWN0dXJlLXJ1bGVzL3J1bGVzLmpzb24nLCB7XG4gICAgICBjb250ZW50czogSlNPTi5zdHJpbmdpZnkoeyBydWxlczogW10gfSwgbnVsbCwgMikgKyAnXFxuJyxcbiAgICB9KTtcbiAgfVxufVxuIl19
@@ -0,0 +1,18 @@
1
+ import { NodeProject } from 'projen/lib/javascript';
2
+ export interface SoftwareFactoryAttestationOptions {
3
+ /**
4
+ * The bare repo name, used to point MANIFEST_FILES at
5
+ * `models/@swamp/software-factory/<repoName>-factory.yaml`.
6
+ */
7
+ readonly repoName: string;
8
+ }
9
+ /**
10
+ * The tamper-evident PR attestation mechanism: `manifest.ts` hashes the
11
+ * files that determine what pre-pr-verify checked, `verify.ts` re-checks
12
+ * those hashes (plus commit/status/freshness) against a PR's embedded
13
+ * `<!-- attestation-manifest -->` block at merge time. Fully generic engine
14
+ * logic — Batman owns and evolves these, so both are managed `TextFile`s.
15
+ */
16
+ export declare class SoftwareFactoryAttestation {
17
+ constructor(project: NodeProject, options: SoftwareFactoryAttestationOptions);
18
+ }
@@ -0,0 +1,221 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SoftwareFactoryAttestation = void 0;
4
+ const projen_1 = require("projen");
5
+ /**
6
+ * The tamper-evident PR attestation mechanism: `manifest.ts` hashes the
7
+ * files that determine what pre-pr-verify checked, `verify.ts` re-checks
8
+ * those hashes (plus commit/status/freshness) against a PR's embedded
9
+ * `<!-- attestation-manifest -->` block at merge time. Fully generic engine
10
+ * logic — Batman owns and evolves these, so both are managed `TextFile`s.
11
+ */
12
+ class SoftwareFactoryAttestation {
13
+ constructor(project, options) {
14
+ new projen_1.TextFile(project, 'attestation/manifest.ts', {
15
+ marker: true,
16
+ lines: [
17
+ '/**',
18
+ ' * Single source of truth for which files\' content determines "was this PR',
19
+ ' * actually verified against the rules it claims." Hashed by both the',
20
+ ' * pre-pr-verify workflow (to embed in the PR\'s attestation) and the',
21
+ ' * verify-attestation GitHub Action (to check nothing drifted since). Keeping',
22
+ ' * one list in one file is what keeps generator and checker from disagreeing',
23
+ ' * about what "the config" even is.',
24
+ ' */',
25
+ "import { createHash } from 'node:crypto';",
26
+ "import { readFileSync } from 'node:fs';",
27
+ "import { join } from 'node:path';",
28
+ '',
29
+ 'export const MANIFEST_FILES: readonly string[] = [',
30
+ " 'workflows/workflow-pre-pr-verify.yaml',",
31
+ " 'workflows/workflow-openspec-plan-verify.yaml',",
32
+ " 'workflows/workflow-openspec-tasks-verify.yaml',",
33
+ ` 'models/@swamp/software-factory/${options.repoName}-factory.yaml',`,
34
+ " 'architecture-rules/rules.json',",
35
+ " 'architecture-rules/check.ts',",
36
+ " 'openspec-gates/check-tasks-complete.ts',",
37
+ " 'CLAUDE.md',",
38
+ '];',
39
+ '',
40
+ 'export interface Manifest {',
41
+ ' generatedAt: string;',
42
+ ' files: Record<string, string>;',
43
+ '}',
44
+ '',
45
+ "export function computeManifest(repoRoot: string = join(__dirname, '..')): Manifest {",
46
+ ' const files: Record<string, string> = {};',
47
+ ' for (const relativePath of MANIFEST_FILES) {',
48
+ ' const contents = readFileSync(join(repoRoot, relativePath));',
49
+ " files[relativePath] = createHash('sha256').update(contents).digest('hex');",
50
+ ' }',
51
+ ' return { generatedAt: new Date().toISOString(), files };',
52
+ '}',
53
+ '',
54
+ 'if (require.main === module) {',
55
+ ' console.log(JSON.stringify(computeManifest(), null, 2));',
56
+ '}',
57
+ ],
58
+ });
59
+ new projen_1.TextFile(project, 'attestation/verify.ts', {
60
+ marker: true,
61
+ lines: [
62
+ '/**',
63
+ ' * Checks the `<!-- attestation-manifest ... -->` block a factory-opened PR',
64
+ ' * embeds in its body (see the open-pr stage in',
65
+ ` * models/@swamp/software-factory/${options.repoName}-factory.yaml) against reality at the`,
66
+ ' * commit being merged. Per stack72.dev/ai-broke-the-assumptions-behind-ci/:',
67
+ ' * this proves the verification-relevant config hasn\'t drifted since the',
68
+ ' * attestation was generated — it does NOT independently prove the tests',
69
+ ' * in that attestation actually ran. That\'s an accepted limitation, not an',
70
+ ' * oversight; see the article and attestation/manifest.ts\'s own comment.',
71
+ ' */',
72
+ "import { readFileSync } from 'node:fs';",
73
+ "import { computeManifest, type Manifest } from './manifest';",
74
+ '',
75
+ 'const MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000; // 7 days',
76
+ '// Global, and deliberately loose about what\'s *between* the marker and the',
77
+ '// block\'s own closing "-->" — PR bodies can (and, it turns out, do) mention',
78
+ '// the literal "<!-- attestation-manifest -->" syntax in prose describing',
79
+ '// this mechanism. Each match is independently validated below; a mention',
80
+ '// with no JSON inside it is skipped rather than treated as a parse error.',
81
+ 'const MANIFEST_BLOCK_RE = /<!--\\s*attestation-manifest([\\s\\S]*?)-->/g;',
82
+ '',
83
+ 'interface EmbeddedManifest extends Manifest {',
84
+ ' commit: string;',
85
+ ' status: string;',
86
+ '}',
87
+ '',
88
+ 'function isEmbeddedManifest(value: unknown): value is EmbeddedManifest {',
89
+ ' const candidate = value as Partial<EmbeddedManifest> | null;',
90
+ ' return (',
91
+ " typeof candidate === 'object' &&",
92
+ ' candidate !== null &&',
93
+ " typeof candidate.commit === 'string' &&",
94
+ " typeof candidate.status === 'string' &&",
95
+ " typeof candidate.generatedAt === 'string' &&",
96
+ " typeof candidate.files === 'object' &&",
97
+ ' candidate.files !== null',
98
+ ' );',
99
+ '}',
100
+ '',
101
+ '// Balanced-brace scan rather than a regex for the JSON itself: the manifest',
102
+ '// has nested objects (files: {...}), so a non-greedy `\\{.*?\\}` would stop at',
103
+ '// the first inner "}" instead of the real end of the object.',
104
+ 'function extractBalancedJson(text: string): string | null {',
105
+ " const start = text.indexOf('{');",
106
+ ' if (start === -1) return null;',
107
+ ' let depth = 0;',
108
+ ' for (let i = start; i < text.length; i++) {',
109
+ " if (text[i] === '{') depth++;",
110
+ " else if (text[i] === '}') {",
111
+ ' depth--;',
112
+ ' if (depth === 0) return text.slice(start, i + 1);',
113
+ ' }',
114
+ ' }',
115
+ ' return null;',
116
+ '}',
117
+ '',
118
+ 'function extractEmbeddedManifest(body: string): EmbeddedManifest {',
119
+ ' let matchedAnyBlock = false;',
120
+ ' for (const match of body.matchAll(MANIFEST_BLOCK_RE)) {',
121
+ ' matchedAnyBlock = true;',
122
+ ' const jsonText = extractBalancedJson(match[1]);',
123
+ ' if (!jsonText) continue;',
124
+ ' try {',
125
+ ' const parsed = JSON.parse(jsonText);',
126
+ ' if (isEmbeddedManifest(parsed)) return parsed;',
127
+ ' } catch {',
128
+ ' // Not this one — keep looking at the next attestation-manifest mention.',
129
+ ' }',
130
+ ' }',
131
+ ' throw new Error(',
132
+ ' matchedAnyBlock',
133
+ " ? 'Found <!-- attestation-manifest --> mention(s) in the PR body, but none contained a complete, valid manifest.'",
134
+ " : 'No <!-- attestation-manifest --> block found in the PR body. This PR was never verified by ' +",
135
+ ' \'pre-pr-verify (or the block was removed) — run the factory\\\'s pre-pr-verify workflow and re-open/update the PR.\',',
136
+ ' );',
137
+ '}',
138
+ '',
139
+ 'function verify(prBody: string, expectedCommit: string): string[] {',
140
+ ' let embedded: EmbeddedManifest;',
141
+ ' try {',
142
+ ' embedded = extractEmbeddedManifest(prBody);',
143
+ ' } catch (err) {',
144
+ ' return [err instanceof Error ? err.message : String(err)];',
145
+ ' }',
146
+ '',
147
+ ' const errors: string[] = [];',
148
+ '',
149
+ ' if (embedded.commit !== expectedCommit) {',
150
+ ' errors.push(',
151
+ ' `Attestation is for commit ${embedded.commit}, but the PR\'s head commit is ${expectedCommit}. ` +',
152
+ " 'New commits were pushed without re-running pre-pr-verify.',",
153
+ ' );',
154
+ ' }',
155
+ '',
156
+ " if (embedded.status !== 'succeeded') {",
157
+ ' errors.push(`Attestation status is "${embedded.status}", not "succeeded".`);',
158
+ ' }',
159
+ '',
160
+ ' const ageMs = Date.now() - Date.parse(embedded.generatedAt);',
161
+ ' if (!Number.isFinite(ageMs)) {',
162
+ ' errors.push(`Attestation generatedAt ("${embedded.generatedAt}") is not a parseable timestamp.`);',
163
+ ' } else if (ageMs > MAX_AGE_MS) {',
164
+ ' errors.push(`Attestation is ${Math.floor(ageMs / (24 * 60 * 60 * 1000))} days old (max ${MAX_AGE_MS / (24 * 60 * 60 * 1000)}) — re-run pre-pr-verify.`);',
165
+ ' }',
166
+ '',
167
+ ' const actual = computeManifest();',
168
+ ' for (const file of Object.keys(actual.files)) {',
169
+ ' if (embedded.files[file] !== actual.files[file]) {',
170
+ ' errors.push(`${file} has changed since the attestation was generated (expected ${embedded.files[file] ?? \'missing\'}, got ${actual.files[file]}).`);',
171
+ ' }',
172
+ ' }',
173
+ ' for (const file of Object.keys(embedded.files)) {',
174
+ ' if (!(file in actual.files)) {',
175
+ ' errors.push(`${file} is in the attestation manifest but no longer in the current file list (attestation/manifest.ts\'s MANIFEST_FILES changed).`);',
176
+ ' }',
177
+ ' }',
178
+ '',
179
+ ' return errors;',
180
+ '}',
181
+ '',
182
+ 'function parseArgs(argv: string[]): { bodyFile: string; commit: string } {',
183
+ " const bodyFile = argv[argv.indexOf('--body-file') + 1];",
184
+ " const commit = argv[argv.indexOf('--commit') + 1];",
185
+ ' if (!bodyFile || !commit) {',
186
+ " throw new Error('Usage: verify.ts --body-file <path> --commit <sha>');",
187
+ ' }',
188
+ ' return { bodyFile, commit };',
189
+ '}',
190
+ '',
191
+ 'if (require.main === module) {',
192
+ ' const { bodyFile, commit } = parseArgs(process.argv.slice(2));',
193
+ " const prBody = readFileSync(bodyFile, 'utf8');",
194
+ ' const errors = verify(prBody, commit);',
195
+ ' if (errors.length > 0) {',
196
+ " console.error('Attestation verification failed:\\n');",
197
+ ' for (const error of errors) {',
198
+ ' console.error(` - ${error}`);',
199
+ ' }',
200
+ ' process.exit(1);',
201
+ ' }',
202
+ ' console.log(`Attestation verified: commit ${commit}, all ${Object.keys(computeManifest().files).length} tracked files match.`);',
203
+ '}',
204
+ ],
205
+ });
206
+ new projen_1.JsonFile(project, 'attestation/tsconfig.json', {
207
+ marker: false,
208
+ obj: {
209
+ extends: '../tsconfig.json',
210
+ compilerOptions: {
211
+ noEmit: true,
212
+ rootDir: '.',
213
+ },
214
+ include: ['*.ts'],
215
+ exclude: ['node_modules'],
216
+ },
217
+ });
218
+ }
219
+ }
220
+ exports.SoftwareFactoryAttestation = SoftwareFactoryAttestation;
221
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiU29mdHdhcmVGYWN0b3J5QXR0ZXN0YXRpb24uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvU29mdHdhcmVGYWN0b3J5QXR0ZXN0YXRpb24udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBQUEsbUNBQTRDO0FBVzVDOzs7Ozs7R0FNRztBQUNILE1BQWEsMEJBQTBCO0lBQ3JDLFlBQVksT0FBb0IsRUFBRSxPQUEwQztRQUMxRSxJQUFJLGlCQUFRLENBQUMsT0FBTyxFQUFFLHlCQUF5QixFQUFFO1lBQy9DLE1BQU0sRUFBRSxJQUFJO1lBQ1osS0FBSyxFQUFFO2dCQUNMLEtBQUs7Z0JBQ0wsNkVBQTZFO2dCQUM3RSx1RUFBdUU7Z0JBQ3ZFLHVFQUF1RTtnQkFDdkUsK0VBQStFO2dCQUMvRSw4RUFBOEU7Z0JBQzlFLHFDQUFxQztnQkFDckMsS0FBSztnQkFDTCwyQ0FBMkM7Z0JBQzNDLHlDQUF5QztnQkFDekMsbUNBQW1DO2dCQUNuQyxFQUFFO2dCQUNGLG9EQUFvRDtnQkFDcEQsNENBQTRDO2dCQUM1QyxtREFBbUQ7Z0JBQ25ELG9EQUFvRDtnQkFDcEQscUNBQXFDLE9BQU8sQ0FBQyxRQUFRLGlCQUFpQjtnQkFDdEUsb0NBQW9DO2dCQUNwQyxrQ0FBa0M7Z0JBQ2xDLDZDQUE2QztnQkFDN0MsZ0JBQWdCO2dCQUNoQixJQUFJO2dCQUNKLEVBQUU7Z0JBQ0YsNkJBQTZCO2dCQUM3Qix3QkFBd0I7Z0JBQ3hCLGtDQUFrQztnQkFDbEMsR0FBRztnQkFDSCxFQUFFO2dCQUNGLHVGQUF1RjtnQkFDdkYsNkNBQTZDO2dCQUM3QyxnREFBZ0Q7Z0JBQ2hELGtFQUFrRTtnQkFDbEUsZ0ZBQWdGO2dCQUNoRixLQUFLO2dCQUNMLDREQUE0RDtnQkFDNUQsR0FBRztnQkFDSCxFQUFFO2dCQUNGLGdDQUFnQztnQkFDaEMsNERBQTREO2dCQUM1RCxHQUFHO2FBQ0o7U0FDRixDQUFDLENBQUM7UUFFSCxJQUFJLGlCQUFRLENBQUMsT0FBTyxFQUFFLHVC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MEM7Z0JBQzFDLDRCQUE0QjtnQkFDNUIsMkRBQTJEO2dCQUMzRCxtQ0FBbUM7Z0JBQ25DLHNDQUFzQztnQkFDdEMsT0FBTztnQkFDUCxzQkFBc0I7Z0JBQ3RCLEtBQUs7Z0JBQ0wsbUlBQW1JO2dCQUNuSSxHQUFHO2FBQ0o7U0FDRixDQUFDLENBQUM7UUFFSCxJQUFJLGlCQUFRLENBQUMsT0FBTyxFQUFFLDJCQUEyQixFQUFFO1lBQ2pELE1BQU0sRUFBRSxLQUFLO1lBQ2IsR0FBRyxFQUFFO2dCQUNILE9BQU8sRUFBRSxrQkFBa0I7Z0JBQzNCLGVBQWUsRUFBRTtvQkFDZixNQUFNLEVBQUUsSUFBSTtvQkFDWixPQUFPLEVBQUUsR0FBRztpQkFDYjtnQkFDRCxPQUFPLEVBQUUsQ0FBQyxNQUFNLENBQUM7Z0JBQ2pCLE9BQU8sRUFBRSxDQUFDLGNBQWMsQ0FBQzthQUMxQjtTQUNGLENBQUMsQ0FBQztJQUNMLENBQUM7Q0FDRjtBQWpORCxnRUFpTkMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBKc29uRmlsZSwgVGV4dEZpbGUgfSBmcm9tICdwcm9qZW4nO1xuaW1wb3J0IHsgTm9kZVByb2plY3QgfSBmcm9tICdwcm9qZW4vbGliL2phdmFzY3JpcHQnO1xuXG5leHBvcnQgaW50ZXJmYWNlIFNvZnR3YXJlRmFjdG9yeUF0dGVzdGF0aW9uT3B0aW9ucyB7XG4gIC8qKlxuICAgKiBUaGUgYmFyZSByZXBvIG5hbWUsIHVzZWQgdG8gcG9pbnQgTUFOSUZFU1RfRklMRVMgYXRcbiAgICogYG1vZGVscy9Ac3dhbXAvc29mdHdhcmUtZmFjdG9yeS88cmVwb05hbWU+LWZhY3RvcnkueWFtbGAuXG4gICAqL1xuICByZWFkb25seSByZXBvTmFtZTogc3RyaW5nO1xufVxuXG4vKipcbiAqIFRoZSB0YW1wZXItZXZpZGVudCBQUiBhdHRlc3RhdGlvbiBtZWNoYW5pc206IGBtYW5pZmVzdC50c2AgaGFzaGVzIHRoZVxuICogZmlsZXMgdGhhdCBkZXRlcm1pbmUgd2hhdCBwcmUtcHItdmVyaWZ5IGNoZWNrZWQsIGB2ZXJpZnkudHNgIHJlLWNoZWNrc1xuICogdGhvc2UgaGFzaGVzIChwbHVzIGNvbW1pdC9zdGF0dXMvZnJlc2huZXNzKSBhZ2FpbnN0IGEgUFIncyBlbWJlZGRlZFxuICogYDwhLS0gYXR0ZXN0YXRpb24tbWFuaWZlc3QgLS0+YCBibG9jayBhdCBtZXJnZSB0aW1lLiBGdWxseSBnZW5lcmljIGVuZ2luZVxuICogbG9naWMg4oCUIEJhdG1hbiBvd25zIGFuZCBldm9sdmVzIHRoZXNlLCBzbyBib3RoIGFyZSBtYW5hZ2VkIGBUZXh0RmlsZWBzLlxuICovXG5leHBvcnQgY2xhc3MgU29mdHdhcmVGYWN0b3J5QXR0ZXN0YXRpb24ge1xuICBjb25zdHJ1Y3Rvcihwcm9qZWN0OiBOb2RlUHJvamVjdCwgb3B0aW9uczogU29mdHdhcmVGYWN0b3J5QXR0ZXN0YXRpb25PcHRpb25zKSB7XG4gICAgbmV3IFRleHRGaWxlKHByb2plY3QsICdhdHRlc3RhdGlvbi9tYW5pZmVzdC50cycsIHtcbiAgICAgIG1hcmtlcjogdHJ1ZSxcbiAgICAgIGxpbmVzOiBbXG4gICAgICAgICcvKionLFxuICAgICAgICAnICogU2luZ2xlIHNvdXJjZSBvZiB0cnV0aCBmb3Igd2hpY2ggZmlsZXNcXCcgY29udGVudCBkZXRlcm1pbmVzIFwid2FzIHRoaXMgUFInLFxuICAgICAgICAnICogYWN0dWFsbHkgdmVyaWZpZWQgYWdhaW5zdCB0aGUgcnVsZXMgaXQgY2xhaW1zLlwiIEhhc2hlZCBieSBib3RoIHRoZScsXG4gICAgICAgICcgKiBwcmUtcHItdmVyaWZ5IHdvcmtmbG93ICh0byBlbWJlZCBpbiB0aGUgUFJcXCdzIGF0dGVzdGF0aW9uKSBhbmQgdGhlJyxcbiAgICAgICAgJyAqIHZlcmlmeS1hdHRlc3RhdGlvbiBHaXRIdWIgQWN0aW9uICh0byBjaGVjayBub3RoaW5nIGRyaWZ0ZWQgc2luY2UpLiBLZWVwaW5nJyxcbiAgICAgICAgJyAqIG9uZSBsaXN0IGluIG9uZSBmaWxlIGlzIHdoYXQga2VlcHMgZ2VuZXJhdG9yIGFuZCBjaGVja2VyIGZyb20gZGlzYWdyZWVpbmcnLFxuICAgICAgICAnICogYWJvdXQgd2hhdCBcInRoZSBjb25maWdcIiBldmVuIGlzLicsXG4gICAgICAgICcgKi8nLFxuICAgICAgICBcImltcG9ydCB7IGNyZWF0ZUhhc2ggfSBmcm9tICdub2RlOmNyeXB0byc7XCIsXG4gICAgICAgIFwiaW1wb3J0IHsgcmVhZEZpbGVTeW5jIH0gZnJvbSAnbm9kZTpmcyc7XCIsXG4gICAgICAgIFwiaW1wb3J0IHsgam9pbiB9IGZyb20gJ25vZGU6cGF0aCc7XCIsXG4gICAgICAgICcnLFxuICAgICAgICAnZXhwb3J0IGNvbnN0IE1BTklGRVNUX0ZJTEVTOiByZWFkb25seSBzdHJpbmdbXSA9IFsnLFxuICAgICAgICBcIiAgJ3dvcmtmbG93cy93b3JrZmxvdy1wcmUtcHItdmVyaWZ5LnlhbWwnLFwiLFxuICAgICAgICBcIiAgJ3dvcmtmbG93cy93b3JrZmxvdy1vcGVuc3BlYy1wbGFuLXZlcmlmeS55YW1sJyxcIixcbiAgICAgICAgXCIgICd3b3JrZmxvd3Mvd29ya2Zsb3ctb3BlbnNwZWMtdGFza3MtdmVyaWZ5LnlhbWwnLFwiLFxuICAgICAgICBgICAnbW9kZWxzL0Bzd2FtcC9zb2Z0d2FyZS1mYWN0b3J5LyR7b3B0aW9ucy5yZXBvTmFtZX0tZmFjdG9yeS55YW1sJyxgLFxuICAgICAgICBcIiAgJ2FyY2hpdGVjdHVyZS1ydWxlcy9ydWxlcy5qc29uJyxcIixcbiAgICAgICAgXCIgICdhcmNoaXRlY3R1cmUtcnVsZXMvY2hlY2sudHMnLFwiLFxuICAgICAgICBcIiAgJ29wZW5zcGVjLWdhdGVzL2NoZWNrLXRhc2tzLWNvbXBsZXRlLnRzJyxcIixcbiAgICAgICAgXCIgICdDTEFVREUubWQnLFwiLFxuICAgICAgICAnXTsnLFxuICAgICAgICAnJyxcbiAgICAgICAgJ2V4cG9ydCBpbnRlcmZhY2UgTWFuaWZlc3QgeycsXG4gICAgICAgICcgIGdlbmVyYXRlZEF0OiBzdHJpbmc7JyxcbiAgICAgICAgJyAgZmlsZXM6IFJlY29yZDxzdHJpbmcsIHN0cmluZz47JyxcbiAgICAgICAgJ30nLFxuICAgICAgICAnJyxcbiAgICAgICAgXCJleHBvcnQgZnVuY3Rpb24gY29tcHV0ZU1hbmlmZXN0KHJlcG9Sb290OiBzdHJpbmcgPSBqb2luKF9fZGlybmFtZSwgJy4uJykpOiBNYW5pZmVzdCB7XCIsXG4gICAgICAgICcgIGNvbnN0IGZpbGVzOiBSZWNvcmQ8c3RyaW5nLCBzdHJpbmc+ID0ge307JyxcbiAgICAgICAgJyAgZm9yIChjb25zdCByZWxhdGl2ZVBhdGggb2YgTUFOSUZFU1RfRklMRVMpIHsnLFxuICAgICAgICAnICAgIGNvbnN0IGNvbnRlbnRzID0gcmVhZEZpbGVTeW5jKGpvaW4ocmVwb1Jvb3QsIHJlbGF0aXZlUGF0aCkpOycsXG4gICAgICAgIFwiICAgIGZpbGVzW3JlbGF0aXZlUGF0aF0gPSBjcmVhdGVIYXNoKCdzaGEyNTYnKS51cGRhdGUoY29udGVudHMpLmRpZ2VzdCgnaGV4Jyk7XCIsXG4gICAgICAgICcgIH0nLFxuICAgICAgICAnICByZXR1cm4geyBnZW5lcmF0ZWRBdDogbmV3IERhdGUoKS50b0lTT1N0cmluZygpLCBmaWxlcyB9OycsXG4gICAgICAgICd9JyxcbiAgICAgICAgJycsXG4gICAgICAgICdpZiAocmVxdWlyZS5tYWluID09PSBtb2R1bGUpIHsnLFxuICAgICAgICAnICBjb25zb2xlLmxvZyhKU09OLnN0cmluZ2lmeShjb21wdXRlTWFuaWZlc3QoKSwgbnVsbCwgMikpOycsXG4gICAgICAgICd9JyxcbiAgICAgIF0sXG4gICAgfSk7XG5cbiAgICBuZXcgVGV4dEZpbGUocHJvamVjdCwgJ2F0dGVzdGF0aW9uL3ZlcmlmeS50cycsIHtcbiAgICAgIG1hcmtlcjogdHJ1ZSxcbiAgICAgIGxpbmVzOiBbXG4gICAgICAgICcvKionLFxuICAgICAgICAnICogQ2hlY2tzIHRoZSBgPCEtLSBhdHRlc3RhdGlvbi1tYW5pZmVzdCAuLi4gLS0+YCBibG9jayBhIGZhY3Rvcnktb3BlbmVkIFBSJyxcbiAgICAgICAgJyAqIGVtYmVkcyBpbiBpdHMgYm9keSAoc2VlIHRoZSBvcGVuLXByIHN0YWdlIGluJyxcbiAgICAgICAgYCAqIG1vZGVscy9Ac3dhbXAvc29mdHdhcmUtZmFjdG9yeS8ke29wdGlvbnMucmVwb05hbWV9LWZhY3RvcnkueWFtbCkgYWdhaW5zdCByZWFsaXR5IGF0IHRoZWAsXG4gICAgICAgICcgKiBjb21taXQgYmVpbmcgbWVyZ2VkLiBQZXIgc3RhY2s3Mi5kZXYvYWktYnJva2UtdGhlLWFzc3VtcHRpb25zLWJlaGluZC1jaS86JyxcbiAgICAgICAgJyAqIHRoaXMgcHJvdmVzIHRoZSB2ZXJpZmljYXRpb24tcmVsZXZhbnQgY29uZmlnIGhhc25cXCd0IGRyaWZ0ZWQgc2luY2UgdGhlJyxcbiAgICAgICAgJyAqIGF0dGVzdGF0aW9uIHdhcyBnZW5lcmF0ZWQg4oCUIGl0IGRvZXMgTk9UIGluZGVwZW5kZW50bHkgcHJvdmUgdGhlIHRlc3RzJyxcbiAgICAgICAgJyAqIGluIHRoYXQgYXR0ZXN0YXRpb24gYWN0dWFsbHkgcmFuLiBUaGF0XFwncyBhbiBhY2NlcHRlZCBsaW1pdGF0aW9uLCBub3QgYW4nLFxuICAgICAgICAnICogb3ZlcnNpZ2h0OyBzZWUgdGhlIGFydGljbGUgYW5kIGF0dGVzdGF0aW9uL21hbmlmZXN0LnRzXFwncyBvd24gY29tbWVudC4nLFxuICAgICAgICAnICovJyxcbiAgICAgICAgXCJpbXBvcnQgeyByZWFkRmlsZVN5bmMgfSBmcm9tICdub2RlOmZzJztcIixcbiAgICAgICAgXCJpbXBvcnQgeyBjb21wdXRlTWFuaWZlc3QsIHR5cGUgTWFuaWZlc3QgfSBmcm9tICcuL21hbmlmZXN0JztcIixcbiAgICAgICAgJycsXG4gICAgICAgICdjb25zdCBNQVhfQUdFX01TID0gNyAqIDI0ICogNjAgKiA2MCAqIDEwMDA7IC8vIDcgZGF5cycsXG4gICAgICAgICcvLyBHbG9iYWwsIGFuZCBkZWxpYmVyYXRlbHkgbG9vc2UgYWJvdXQgd2hhdFxcJ3MgKmJldHdlZW4qIHRoZSBtYXJrZXIgYW5kIHRoZScsXG4gICAgICAgICcvLyBibG9ja1xcJ3Mgb3duIGNsb3NpbmcgXCItLT5cIiDigJQgUFIgYm9kaWVzIGNhbiAoYW5kLCBpdCB0dXJucyBvdXQsIGRvKSBtZW50aW9uJyxcbiAgICAgICAgJy8vIHRoZSBsaXRlcmFsIFwiPCEtLSBhdHRlc3RhdGlvbi1tYW5pZmVzdCAtLT5cIiBzeW50YXggaW4gcHJvc2UgZGVzY3JpYmluZycsXG4gICAgICAgICcvLyB0aGlzIG1lY2hhbmlzbS4gRWFjaCBtYXRjaCBpcyBpbmRlcGVuZGVudGx5IHZhbGlkYXRlZCBiZWxvdzsgYSBtZW50aW9uJyxcbiAgICAgICAgJy8vIHdpdGggbm8gSlNPTiBpbnNpZGUgaXQgaXMgc2tpcHBlZCByYXRoZXIgdGhhbiB0cmVhdGVkIGFzIGEgcGFyc2UgZXJyb3IuJyxcbiAgICAgICAgJ2NvbnN0IE1BTklGRVNUX0JMT0NLX1JFID0gLzwhLS1cXFxccyphdHRlc3RhdGlvbi1tYW5pZmVzdChbXFxcXHNcXFxcU10qPyktLT4vZzsnLFxuICAgICAgICAnJyxcbiAgICAgICAgJ2ludGVyZmFjZSBFbWJlZGRlZE1hbmlmZXN0IGV4dGVuZHMgTWFuaWZlc3QgeycsXG4gICAgICAgICcgIGNvbW1pdDogc3RyaW5nOycsXG4gICAgICAgICcgIHN0YXR1czogc3RyaW5nOycsXG4gICAgICAgICd9JyxcbiAgICAgICAgJycsXG4gICAgICAgICdmdW5jdGlvbiBpc0VtYmVkZGVkTWFuaWZlc3QodmFsdWU6IHVua25vd24pOiB2YWx1ZSBpcyBFbWJlZGRlZE1hbmlmZXN0IHsnLFxuICAgICAgICAnICBjb25zdCBjYW5kaWRhdGUgPSB2YWx1ZSBhcyBQYXJ0aWFsPEVtYmVkZGVkTWFuaWZlc3Q+IHwgbnVsbDsnLFxuICAgICAgICAnICByZXR1cm4gKCcsXG4gICAgICAgIFwiICAgIHR5cGVvZiBjYW5kaWRhdGUgPT09ICdvYmplY3QnICYmXCIsXG4gICAgICAgICcgICAgY2FuZGlkYXRlICE9PSBudWxsICYmJyxcbiAgICAgICAgXCIgICAgdHlwZW9mIGNhbmRpZGF0ZS5jb21taXQgPT09ICdzdHJpbmcnICYmXCIsXG4gICAgICAgIFwiICAgIHR5cGVvZiBjYW5kaWRhdGUuc3RhdHVzID09PSAnc3RyaW5nJyAmJlwiLFxuICAgICAgICBcIiAgICB0eXBlb2YgY2FuZGlkYXRlLmdlbmVyYXRlZEF0ID09PSAnc3RyaW5nJyAmJlwiLFxuICAgICAgICBcIiAgICB0eXBlb2YgY2FuZGlkYXRlLmZpbGVzID09PSAnb2JqZWN0JyAmJlwiLFxuICAgICAgICAnICAgIGNhbmRpZGF0ZS5maWxlcyAhPT0gbnVsbCcsXG4gICAgICAgICcgICk7JyxcbiAgICAgICAgJ30nLFxuICAgICAgICAnJyxcbiAgICAgICAgJy8vIEJhbGFuY2VkLWJyYWNlIHNjYW4gcmF0aGVyIHRoYW4gYSByZWdleCBmb3IgdGhlIEpTT04gaXRzZWxmOiB0aGUgbWFuaWZlc3QnLFxuICAgICAgICAnLy8gaGFzIG5lc3RlZCBvYmplY3RzIChmaWxlczogey4uLn0pLCBzbyBhIG5vbi1ncmVlZHkgYFxcXFx7Lio/XFxcXH1gIHdvdWxkIHN0b3AgYXQnLFxuICAgICAgICAnLy8gdGhlIGZpcnN0IGlubmVyIFwifVwiIGluc3RlYWQgb2YgdGhlIHJlYWwgZW5kIG9mIHRoZSBvYmplY3QuJyxcbiAgICAgICAgJ2Z1bmN0aW9uIGV4dHJhY3RCYWxhbmNlZEpzb24odGV4dDogc3RyaW5nKTogc3RyaW5nIHwgbnVsbCB7JyxcbiAgICAgICAgXCIgIGNvbnN0IHN0YXJ0ID0gdGV4dC5pbmRleE9mKCd7Jyk7XCIsXG4gICAgICAgICcgIGlmIChzdGFydCA9PT0gLTEpIHJldHVybiBudWxsOycsXG4gICAgICAgICcgIGxldCBkZXB0aCA9IDA7JyxcbiAgICAgICAgJyAgZm9yIChsZXQgaSA9IHN0YXJ0OyBpIDwgdGV4dC5sZW5ndGg7IGkrKykgeycsXG4gICAgICAgIFwiICAgIGlmICh0ZXh0W2ldID09PSAneycpIGRlcHRoKys7XCIsXG4gICAgICAgIFwiICAgIGVsc2UgaWYgKHRleHRbaV0gPT09ICd9Jykge1wiLFxuICAgICAgICAnICAgICAgZGVwdGgtLTsnLFxuICAgICAgICAnICAgICAgaWYgKGRlcHRoID09PSAwKSByZXR1cm4gdGV4dC5zbGljZShzdGFydCwgaSArIDEpOycsXG4gICAgICAgICcgICAgfScsXG4gICAgICAgICcgIH0nLFxuICAgICAgICAnICByZXR1cm4gbnVsbDsnLFxuICAgICAgICAnfScsXG4gICAgICAgICcnLFxuICAgICAgICAnZnVuY3Rpb24gZXh0cmFjdEVtYmVkZGVkTWFuaWZlc3QoYm9keTogc3RyaW5nKTogRW1iZWRkZWRNYW5pZmVzdCB7JyxcbiAgICAgICAgJyAgbGV0IG1hdGNoZWRBbnlCbG9jayA9IGZhbHNlOycsXG4gICAgICAgICcgIGZvciAoY29uc3QgbWF0Y2ggb2YgYm9keS5tYXRjaEFsbChNQU5JRkVTVF9CTE9DS19SRSkpIHsnLFxuICAgICAgICAnICAgIG1hdGNoZWRBbnlCbG9jayA9IHRydWU7JyxcbiAgICAgICAgJyAgICBjb25zdCBqc29uVGV4dCA9IGV4dHJhY3RCYWxhbmNlZEpzb24obWF0Y2hbMV0pOycsXG4gICAgICAgICcgICAgaWYgKCFqc29uVGV4dCkgY29udGludWU7JyxcbiAgICAgICAgJyAgICB0cnkgeycsXG4gICAgICAgICcgICAgICBjb25zdCBwYXJzZWQgPSBKU09OLnBhcnNlKGpzb25UZXh0KTsnLFxuICAgICAgICAnICAgICAgaWYgKGlzRW1iZWRkZWRNYW5pZmVzdChwYXJzZWQpKSByZXR1cm4gcGFyc2VkOycsXG4gICAgICAgICcgICAgfSBjYXRjaCB7JyxcbiAgICAgICAgJyAgICAgIC8vIE5vdCB0aGlzIG9uZSDigJQga2VlcCBsb29raW5nIGF0IHRoZSBuZXh0IGF0dGVzdGF0aW9uLW1hbmlmZXN0IG1lbnRpb24uJyxcbiAgICAgICAgJyAgICB9JyxcbiAgICAgICAgJyAgfScsXG4gICAgICAgICcgIHRocm93IG5ldyBFcnJvcignLFxuICAgICAgICAnICAgIG1hdGNoZWRBbnlCbG9jaycsXG4gICAgICAgIFwiICAgICAgPyAnRm91bmQgPCEtLSBhdHRlc3RhdGlvbi1tYW5pZmVzdCAtLT4gbWVudGlvbihzKSBpbiB0aGUgUFIgYm9keSwgYnV0IG5vbmUgY29udGFpbmVkIGEgY29tcGxldGUsIHZhbGlkIG1hbmlmZXN0LidcIixcbiAgICAgICAgXCIgICAgICA6ICdObyA8IS0tIGF0dGVzdGF0aW9uLW1hbmlmZXN0IC0tPiBibG9jayBmb3VuZCBpbiB0aGUgUFIgYm9keS4gVGhpcyBQUiB3YXMgbmV2ZXIgdmVyaWZpZWQgYnkgJyArXCIsXG4gICAgICAgICcgICAgICAgIFxcJ3ByZS1wci12ZXJpZnkgKG9yIHRoZSBibG9jayB3YXMgcmVtb3ZlZCkg4oCUIHJ1biB0aGUgZmFjdG9yeVxcXFxcXCdzIHByZS1wci12ZXJpZnkgd29ya2Zsb3cgYW5kIHJlLW9wZW4vdXBkYXRlIHRoZSBQUi5cXCcsJyxcbiAgICAgICAgJyAgKTsnLFxuICAgICAgICAnfScsXG4gICAgICAgICcnLFxuICAgICAgICAnZnVuY3Rpb24gdmVyaWZ5KHByQm9keTogc3RyaW5nLCBleHBlY3RlZENvbW1pdDogc3RyaW5nKTogc3RyaW5nW10geycsXG4gICAgICAgICcgIGxldCBlbWJlZGRlZDogRW1iZWRkZWRNYW5pZmVzdDsnLFxuICAgICAgICAnICB0cnkgeycsXG4gICAgICAgICcgICAgZW1iZWRkZWQgPSBleHRyYWN0RW1iZWRkZWRNYW5pZmVzdChwckJvZHkpOycsXG4gICAgICAgICcgIH0gY2F0Y2ggKGVycikgeycsXG4gICAgICAgICcgICAgcmV0dXJuIFtlcnIgaW5zdGFuY2VvZiBFcnJvciA/IGVyci5tZXNzYWdlIDogU3RyaW5nKGVycildOycsXG4gICAgICAgICcgIH0nLFxuICAgICAgICAnJyxcbiAgICAgICAgJyAgY29uc3QgZXJyb3JzOiBzdHJpbmdbXSA9IFtdOycsXG4gICAgICAgICcnLFxuICAgICAgICAnICBpZiAoZW1iZWRkZWQuY29tbWl0ICE9PSBleHBlY3RlZENvbW1pdCkgeycsXG4gICAgICAgICcgICAgZXJyb3JzLnB1c2goJyxcbiAgICAgICAgJyAgICAgIGBBdHRlc3RhdGlvbiBpcyBmb3IgY29tbWl0ICR7ZW1iZWRkZWQuY29tbWl0fSwgYnV0IHRoZSBQUlxcJ3MgaGVhZCBjb21taXQgaXMgJHtleHBlY3RlZENvbW1pdH0uIGAgKycsXG4gICAgICAgIFwiICAgICAgJ05ldyBjb21taXRzIHdlcmUgcHVzaGVkIHdpdGhvdXQgcmUtcnVubmluZyBwcmUtcHItdmVyaWZ5LicsXCIsXG4gICAgICAgICcgICAgKTsnLFxuICAgICAgICAnICB9JyxcbiAgICAgICAgJycsXG4gICAgICAgIFwiICBpZiAoZW1iZWRkZWQuc3RhdHVzICE9PSAnc3VjY2VlZGVkJykge1wiLFxuICAgICAgICAnICAgIGVycm9ycy5wdXNoKGBBdHRlc3RhdGlvbiBzdGF0dXMgaXMgXCIke2VtYmVkZGVkLnN0YXR1c31cIiwgbm90IFwic3VjY2VlZGVkXCIuYCk7JyxcbiAgICAgICAgJyAgfScsXG4gICAgICAgICcnLFxuICAgICAgICAnICBjb25zdCBhZ2VNcyA9IERhdGUubm93KCkgLSBEYXRlLnBhcnNlKGVtYmVkZGVkLmdlbmVyYXRlZEF0KTsnLFxuICAgICAgICAnICBpZiAoIU51bWJlci5pc0Zpbml0ZShhZ2VNcykpIHsnLFxuICAgICAgICAnICAgIGVycm9ycy5wdXNoKGBBdHRlc3RhdGlvbiBnZW5lcmF0ZWRBdCAoXCIke2VtYmVkZGVkLmdlbmVyYXRlZEF0fVwiKSBpcyBub3QgYSBwYXJzZWFibGUgdGltZXN0YW1wLmApOycsXG4gICAgICAgICcgIH0gZWxzZSBpZiAoYWdlTXMgPiBNQVhfQUdFX01TKSB7JyxcbiAgICAgICAgJyAgICBlcnJvcnMucHVzaChgQXR0ZXN0YXRpb24gaXMgJHtNYXRoLmZsb29yKGFnZU1zIC8gKDI0ICogNjAgKiA2MCAqIDEwMDApKX0gZGF5cyBvbGQgKG1heCAke01BWF9BR0VfTVMgLyAoMjQgKiA2MCAqIDYwICogMTAwMCl9KSDigJQgcmUtcnVuIHByZS1wci12ZXJpZnkuYCk7JyxcbiAgICAgICAgJyAgfScsXG4gICAgICAgICcnLFxuICAgICAgICAnICBjb25zdCBhY3R1YWwgPSBjb21wdXRlTWFuaWZlc3QoKTsnLFxuICAgICAgICAnICBmb3IgKGNvbnN0IGZpbGUgb2YgT2JqZWN0LmtleXMoYWN0dWFsLmZpbGVzKSkgeycsXG4gICAgICAgICcgICAgaWYgKGVtYmVkZGVkLmZpbGVzW2ZpbGVdICE9PSBhY3R1YWwuZmlsZXNbZmlsZV0pIHsnLFxuICAgICAgICAnICAgICAgZXJyb3JzLnB1c2goYCR7ZmlsZX0gaGFzIGNoYW5nZWQgc2luY2UgdGhlIGF0dGVzdGF0aW9uIHdhcyBnZW5lcmF0ZWQgKGV4cGVjdGVkICR7ZW1iZWRkZWQuZmlsZXNbZmlsZV0gPz8gXFwnbWlzc2luZ1xcJ30sIGdvdCAke2FjdHVhbC5maWxlc1tmaWxlXX0pLmApOycsXG4gICAgICAgICcgICAgfScsXG4gICAgICAgICcgIH0nLFxuICAgICAgICAnICBmb3IgKGNvbnN0IGZpbGUgb2YgT2JqZWN0LmtleXMoZW1iZWRkZWQuZmlsZXMpKSB7JyxcbiAgICAgICAgJyAgICBpZiAoIShmaWxlIGluIGFjdHVhbC5maWxlcykpIHsnLFxuICAgICAgICAnICAgICAgZXJyb3JzLnB1c2goYCR7ZmlsZX0gaXMgaW4gdGhlIGF0dGVzdGF0aW9uIG1hbmlmZXN0IGJ1dCBubyBsb25nZXIgaW4gdGhlIGN1cnJlbnQgZmlsZSBsaXN0IChhdHRlc3RhdGlvbi9tYW5pZmVzdC50c1xcJ3MgTUFOSUZFU1RfRklMRVMgY2hhbmdlZCkuYCk7JyxcbiAgICAgICAgJyAgICB9JyxcbiAgICAgICAgJyAgfScsXG4gICAgICAgICcnLFxuICAgICAgICAnICByZXR1cm4gZXJyb3JzOycsXG4gICAgICAgICd9JyxcbiAgICAgICAgJycsXG4gICAgICAgICdmdW5jdGlvbiBwYXJzZUFyZ3MoYXJndjogc3RyaW5nW10pOiB7IGJvZHlGaWxlOiBzdHJpbmc7IGNvbW1pdDogc3RyaW5nIH0geycsXG4gICAgICAgIFwiICBjb25zdCBib2R5RmlsZSA9IGFyZ3ZbYXJndi5pbmRleE9mKCctLWJvZHktZmlsZScpICsgMV07XCIsXG4gICAgICAgIFwiICBjb25zdCBjb21taXQgPSBhcmd2W2FyZ3YuaW5kZXhPZignLS1jb21taXQnKSArIDFdO1wiLFxuICAgICAgICAnICBpZiAoIWJvZHlGaWxlIHx8ICFjb21taXQpIHsnLFxuICAgICAgICBcIiAgICB0aHJvdyBuZXcgRXJyb3IoJ1VzYWdlOiB2ZXJpZnkudHMgLS1ib2R5LWZpbGUgPHBhdGg+IC0tY29tbWl0IDxzaGE+Jyk7XCIsXG4gICAgICAgICcgIH0nLFxuICAgICAgICAnICByZXR1cm4geyBib2R5RmlsZSwgY29tbWl0IH07JyxcbiAgICAgICAgJ30nLFxuICAgICAgICAnJyxcbiAgICAgICAgJ2lmIChyZXF1aXJlLm1haW4gPT09IG1vZHVsZSkgeycsXG4gICAgICAgICcgIGNvbnN0IHsgYm9keUZpbGUsIGNvbW1pdCB9ID0gcGFyc2VBcmdzKHByb2Nlc3MuYXJndi5zbGljZSgyKSk7JyxcbiAgICAgICAgXCIgIGNvbnN0IHByQm9keSA9IHJlYWRGaWxlU3luYyhib2R5RmlsZSwgJ3V0ZjgnKTtcIixcbiAgICAgICAgJyAgY29uc3QgZXJyb3JzID0gdmVyaWZ5KHByQm9keSwgY29tbWl0KTsnLFxuICAgICAgICAnICBpZiAoZXJyb3JzLmxlbmd0aCA+IDApIHsnLFxuICAgICAgICBcIiAgICBjb25zb2xlLmVycm9yKCdBdHRlc3RhdGlvbiB2ZXJpZmljYXRpb24gZmFpbGVkOlxcXFxuJyk7XCIsXG4gICAgICAgICcgICAgZm9yIChjb25zdCBlcnJvciBvZiBlcnJvcnMpIHsnLFxuICAgICAgICAnICAgICAgY29uc29sZS5lcnJvcihgICAtICR7ZXJyb3J9YCk7JyxcbiAgICAgICAgJyAgICB9JyxcbiAgICAgICAgJyAgICBwcm9jZXNzLmV4aXQoMSk7JyxcbiAgICAgICAgJyAgfScsXG4gICAgICAgICcgIGNvbnNvbGUubG9nKGBBdHRlc3RhdGlvbiB2ZXJpZmllZDogY29tbWl0ICR7Y29tbWl0fSwgYWxsICR7T2JqZWN0LmtleXMoY29tcHV0ZU1hbmlmZXN0KCkuZmlsZXMpLmxlbmd0aH0gdHJhY2tlZCBmaWxlcyBtYXRjaC5gKTsnLFxuICAgICAgICAnfScsXG4gICAgICBdLFxuICAgIH0pO1xuXG4gICAgbmV3IEpzb25GaWxlKHByb2plY3QsICdhdHRlc3RhdGlvbi90c2NvbmZpZy5qc29uJywge1xuICAgICAgbWFya2VyOiBmYWxzZSxcbiAgICAgIG9iajoge1xuICAgICAgICBleHRlbmRzOiAnLi4vdHNjb25maWcuanNvbicsXG4gICAgICAgIGNvbXBpbGVyT3B0aW9uczoge1xuICAgICAgICAgIG5vRW1pdDogdHJ1ZSxcbiAgICAgICAgICByb290RGlyOiAnLicsXG4gICAgICAgIH0sXG4gICAgICAgIGluY2x1ZGU6IFsnKi50cyddLFxuICAgICAgICBleGNsdWRlOiBbJ25vZGVfbW9kdWxlcyddLFxuICAgICAgfSxcbiAgICB9KTtcbiAgfVxufVxuIl19
@@ -0,0 +1,45 @@
1
+ import { NodeProject } from 'projen/lib/javascript';
2
+ import { WorkflowActionVersions } from './WorkflowActionVersions';
3
+ export interface SoftwareFactoryFilesOptions {
4
+ /**
5
+ * The bare repo name (e.g. `expense-tracker`), used to derive the factory's
6
+ * model names (`<repoName>-factory`, `<repoName>-issues`, `<repoName>-pr`,
7
+ * `<repoName>-shell*`) and vault name (`<repoName>-secrets`).
8
+ */
9
+ readonly repoName: string;
10
+ /**
11
+ * The branch the factory opens pull requests against.
12
+ *
13
+ * @default 'main'
14
+ */
15
+ readonly baseBranch?: string;
16
+ /**
17
+ * Overrides for the pinned action versions used by `verify-attestation.yml`.
18
+ */
19
+ readonly actionVersions?: WorkflowActionVersions;
20
+ }
21
+ /**
22
+ * Backports exman's swamp "software factory" pattern
23
+ * (macedon-technologies/exman#13) as generated scaffolding: a
24
+ * `@swamp/software-factory` state machine that drives a GitHub issue from
25
+ * intake through an opened PR, enforcing OpenSpec usage, an architecture
26
+ * rule registry, and a pre-PR verification gate whose attestation is
27
+ * re-checked at merge time.
28
+ *
29
+ * What swamp itself would generate (extension docs pulled via
30
+ * `swamp extension pull`, `.swamp.yaml`, the vault, and the "swamp managed
31
+ * section" in CLAUDE.md) is deliberately NOT hand-templated here — those are
32
+ * produced by the real `swamp` CLI. `swamp init` is idempotent (a no-op if
33
+ * the repo is already initialized), so it's wired into the project's
34
+ * `install`/`install:ci` tasks and runs automatically on every install,
35
+ * guarded so it's a silent no-op on a machine without `swamp` on PATH (e.g.
36
+ * CI). The extension pulls and vault creation are one-time, not idempotent
37
+ * in a way that's safe to repeat on every install, so they stay in the
38
+ * `swamp:bootstrap` task this construct adds. Only exman's own authored
39
+ * config (the factory's state machine, the architecture-rule engine, the
40
+ * attestation mechanism, the OpenSpec task-completion gate, and the 3 swamp
41
+ * workflows that wire them together) is templated.
42
+ */
43
+ export declare class SoftwareFactoryFiles {
44
+ constructor(project: NodeProject, options: SoftwareFactoryFilesOptions);
45
+ }