jackherd 1.6.0 → 1.7.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/CHANGELOG.md +31 -0
- package/INSTALL.md +176 -0
- package/README.md +260 -315
- package/assets/branding/README.md +7 -3
- package/assets/branding/jackherd-logo-dark-1024.png +0 -0
- package/assets/branding/jackherd-logo-dark-512.png +0 -0
- package/assets/branding/jackherd-logo-dark.svg +1 -1
- package/assets/icons/bark-dark.svg +1 -0
- package/assets/icons/bark.svg +1 -0
- package/assets/icons/bowl-dark.svg +1 -0
- package/assets/icons/bowl.svg +1 -0
- package/assets/icons/decide-dark.svg +1 -0
- package/assets/icons/decide.svg +1 -0
- package/assets/icons/ear-dark.svg +1 -0
- package/assets/icons/ear.svg +1 -0
- package/assets/icons/lanes-dark.svg +1 -0
- package/assets/icons/lanes.svg +1 -0
- package/assets/icons/plan-dark.svg +1 -0
- package/assets/icons/plan.svg +1 -0
- package/assets/icons/space.svg +1 -0
- package/dist/{chunk-QJSXHHQ3.js → chunk-OBUWTQ6Z.js} +25 -0
- package/dist/config.d.ts +14 -3
- package/dist/config.js +111 -10
- package/dist/plan-format.d.ts +180 -77
- package/dist/plan-format.js +292 -93
- package/docs/lanes-dark.svg +50 -0
- package/docs/lanes.svg +50 -1
- package/docs/skills-dark.svg +65 -0
- package/docs/skills.svg +65 -1
- package/docs/workflow-dark.svg +138 -0
- package/docs/workflow.svg +135 -169
- package/herdr-plugin/herdr-plugin.toml +6 -6
- package/herdr-plugin/open-plans.ts +1 -1
- package/herdr-plugin/restore-plans.ts +7 -7
- package/install.sh +1 -1
- package/kit/hooks/hooks/approval-gate/decision.ts +61 -0
- package/kit/hooks/hooks/approval-gate/herdr-panes.ts +24 -0
- package/kit/hooks/hooks/approval-gate/index.ts +69 -0
- package/kit/hooks/hooks/approval-gate/lane.ts +14 -0
- package/kit/hooks/hooks/approval-gate/request.ts +111 -0
- package/kit/hooks/hooks/approval-gate/run.ts +94 -0
- package/kit/hooks/hooks/approval-gate/settings.ts +26 -0
- package/kit/hooks/hooks/approval-gate/store.ts +70 -0
- package/kit/hooks/hooks/approval-gate/transcripts.ts +40 -0
- package/kit/hooks/hooks/merge-guard/approval.ts +11 -74
- package/kit/hooks/hooks/merge-guard/run.ts +2 -1
- package/kit/hooks/hooks/model-gate/agent-definition.ts +13 -0
- package/kit/hooks/hooks/model-gate/index.ts +58 -0
- package/kit/hooks/hooks/model-gate/run.ts +63 -0
- package/kit/hooks/hooks/model-log/index.ts +60 -0
- package/kit/hooks/hooks/model-log/run.ts +57 -0
- package/kit/hooks/hooks/model-log/store.ts +40 -0
- package/kit/hooks/hooks/model-log/transcript.ts +37 -0
- package/kit/hooks/hooks/shared/checkout.ts +22 -0
- package/kit/hooks/hooks/shared/command.ts +6 -0
- package/kit/hooks/hooks/shared/config.ts +21 -0
- package/kit/hooks/hooks/shared/dialog-answer.ts +5 -0
- package/kit/hooks/hooks/shared/dialog-answers.ts +74 -0
- package/kit/hooks/hooks/shared/files.ts +19 -0
- package/kit/hooks/hooks/shared/fresh.ts +4 -0
- package/kit/hooks/hooks/shared/json.ts +4 -0
- package/kit/hooks/hooks/shared/model-family.ts +48 -0
- package/kit/hooks/hooks/shared/plan-model-table.ts +41 -0
- package/kit/hooks/hooks/shared/private-directory.ts +29 -0
- package/kit/hooks/hooks/shared/repository-directory.ts +14 -0
- package/kit/hooks/hooks/shared/subagent-phase.ts +99 -0
- package/kit/hooks/hooks.json +25 -0
- package/kit/workflow/skills/follow/SKILL.md +4 -3
- package/kit/workflow/skills/implement/SKILL.md +6 -6
- package/kit/workflow/skills/orchestrate/SKILL.md +113 -30
- package/kit/workflow/skills/plan/SKILL.md +5 -5
- package/kit/workflow/skills/start/SKILL.md +2 -2
- package/package.json +4 -1
- package/schemas/jackherd.schema.json +43 -5
- package/src/check/command.ts +31 -4
- package/src/check/deferral-arrows.ts +1 -1
- package/src/check/parallel-report.ts +23 -0
- package/src/check/plan-structure-check.ts +120 -53
- package/src/cli.ts +8 -2
- package/src/config/nested-quantifier.ts +73 -0
- package/src/config/repository-directory.ts +11 -0
- package/src/config/schema.ts +63 -8
- package/src/plan-format/board-prose.ts +36 -0
- package/src/plan-format/dependencies.ts +227 -0
- package/src/plan-format/documentation-gate.ts +16 -0
- package/src/plan-format/index.ts +23 -1
- package/src/plan-format/lanes.ts +35 -1
- package/src/plan-format/model-column.ts +52 -0
- package/src/plan-format/phases.ts +16 -1
- package/src/plan-format/plan-structure.ts +10 -28
- package/src/plan-format/progress.ts +7 -29
- package/src/plans/command.ts +3 -3
- package/src/plans/current-phase.ts +71 -0
- package/src/plans/ensure-panes.ts +2 -2
- package/src/plans/herd-command.ts +4 -0
- package/src/plans/lane-discovery.ts +14 -2
- package/src/plans/lane-tickets.ts +69 -0
- package/src/plans/layout.ts +182 -88
- package/src/plans/live.ts +5 -1
- package/src/plans/phase-models-read.ts +37 -0
- package/src/plans/phase-models.ts +70 -0
- package/src/plans/plan-panes.ts +8 -7
- package/src/plans/pull-requests.ts +78 -4
- package/src/plans/selection.ts +1 -40
- package/src/plans/settings.ts +9 -3
- package/src/plans/shown-with-more.ts +5 -0
- package/src/plans/snapshot.ts +82 -8
- package/src/plans/sprint-html.ts +2 -2
- package/src/plans/supervisor.ts +5 -4
- package/src/plans/symbols.ts +9 -6
- package/src/plans/texts.ts +30 -13
- package/src/plans/view.ts +1 -0
- package/src/sync/herdr-plugin.ts +1 -1
- package/src/sync/next-steps.ts +1 -1
- package/src/sync/render.ts +2 -1
- package/src/sync/setup-questions.ts +2 -2
- package/src/terminal/text.ts +4 -0
- package/src/terminal/types.ts +1 -1
- package/src/vocabulary/index.ts +39 -0
- package/src/watch/approvals.ts +96 -0
- package/src/watch/command.ts +154 -50
- package/src/watch/failed-tests.ts +52 -0
- package/src/watch/failure-read.ts +56 -0
- package/src/watch/failures.ts +107 -0
- package/src/watch/feedback-item.ts +21 -0
- package/src/watch/feedback-read.ts +2 -1
- package/src/watch/feedback.ts +37 -28
- package/src/watch/gh-process.ts +53 -0
- package/src/watch/gh.ts +10 -20
- package/src/watch/heartbeat-progress.ts +1 -1
- package/src/watch/lane-model.ts +54 -0
- package/src/watch/lane-parallel.ts +46 -0
- package/src/watch/lane-progress.ts +28 -4
- package/src/watch/lane-report.ts +62 -9
- package/src/watch/lane-sources.ts +1 -1
- package/src/watch/no-finding.ts +47 -0
- package/src/watch/plan-badges-read.ts +19 -7
- package/src/watch/plan-badges.ts +59 -7
- package/src/watch/plan-views.ts +5 -5
- package/src/watch/previews.ts +3 -9
- package/src/watch/pull-requests.ts +12 -2
- package/src/watch/recorded-reads.ts +25 -0
- package/src/watch/source-fields.ts +4 -0
- package/src/watch/source-tickets.ts +99 -0
- package/src/watch/sources.ts +62 -15
- package/src/watch/texts.ts +21 -1
- package/src/watch/watch-defaults.ts +8 -0
- package/src/watch/watch-events.ts +64 -4
- package/src/watch/watcher-lock.ts +1 -1
- package/src/watch/workflow-runs.ts +5 -7
- package/docs/lanes.mmd +0 -9
- package/docs/skills.mmd +0 -10
package/dist/plan-format.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ADDRESS_FORM,
|
|
3
|
+
APPROVAL_TEXTS,
|
|
3
4
|
LANGUAGE_NAME,
|
|
4
5
|
PLAN_LANGUAGE,
|
|
5
6
|
PLAN_STATUS,
|
|
@@ -10,7 +11,10 @@ import {
|
|
|
10
11
|
isPlanLanguage,
|
|
11
12
|
phaseHeading,
|
|
12
13
|
sectionHeading
|
|
13
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-OBUWTQ6Z.js";
|
|
15
|
+
|
|
16
|
+
// src/plan-format/lanes.ts
|
|
17
|
+
import { posix } from "path";
|
|
14
18
|
|
|
15
19
|
// src/plan-format/terminal-text.ts
|
|
16
20
|
var UNSAFE_RANGES = [
|
|
@@ -88,63 +92,12 @@ var allTables = (lines) => {
|
|
|
88
92
|
return tables;
|
|
89
93
|
};
|
|
90
94
|
|
|
91
|
-
// src/plan-format/drift.ts
|
|
92
|
-
var OPEN_TABLE_CELL = /⬜/;
|
|
93
|
-
var DRIFT_WORDS = {
|
|
94
|
-
de: {
|
|
95
|
-
inFlight: /^(Draft|In Progress|Proposed|Open|Planned|Review-Ready|Aktiv)\b/i,
|
|
96
|
-
completion: /\b(done|abgeschlossen|umgesetzt|implementiert|implemented|fertig)\b/i,
|
|
97
|
-
open: /\b(offen|ausstehend|deferred|zurückgestellt)\b/i,
|
|
98
|
-
architecture: /\b(arc42|ADR|Architekturdokumentation|Dokumentation)\b/i,
|
|
99
|
-
architectureHeading: "Architekturdokumentation",
|
|
100
|
-
selfReported: (statusText) => `Status nennt offene Doku-Arbeit: "${statusText.trim()}"`,
|
|
101
|
-
silent: "arc42-Abschlussphase hat offene \u2B1C-Zeilen trotz abgeschlossenem Status"
|
|
102
|
-
},
|
|
103
|
-
en: {
|
|
104
|
-
inFlight: /^(Draft|In Progress|Proposed|Open|Planned|Review-Ready|Active)\b/i,
|
|
105
|
-
completion: /\b(done|completed|implemented|finished)\b/i,
|
|
106
|
-
open: /\b(open|outstanding|pending|deferred)\b/i,
|
|
107
|
-
architecture: /\b(arc42|ADR|architecture documentation|documentation)\b/i,
|
|
108
|
-
architectureHeading: "Architecture documentation",
|
|
109
|
-
selfReported: (statusText) => `Status names open documentation work: "${statusText.trim()}"`,
|
|
110
|
-
silent: "The arc42 closing phase has open \u2B1C rows despite a completed status"
|
|
111
|
-
}
|
|
112
|
-
};
|
|
113
|
-
var statusLine = (content, name) => content.match(new RegExp(`^\\*\\*${escapeRegExp(name)}:\\*\\*\\s*(.+)$`, "m"))?.[1];
|
|
114
|
-
var architectureSection = (content, heading) => content.match(new RegExp(`#{2,4}[^\\n]*${escapeRegExp(heading)}[^\\n]*\\n([\\s\\S]*?)(?=\\n#{1,4} |$)`))?.[1];
|
|
115
|
-
var findDrift = (file, content, language) => {
|
|
116
|
-
const statusText = statusLine(content, PLAN_VOCABULARY[language].header.status);
|
|
117
|
-
const words = DRIFT_WORDS[language];
|
|
118
|
-
if (!statusText || words.inFlight.test(statusText.trim())) return [];
|
|
119
|
-
const completed = words.completion.test(statusText);
|
|
120
|
-
const caveat = words.open.test(statusText);
|
|
121
|
-
const selfReported = completed && caveat && words.architecture.test(statusText) ? words.selfReported(statusText) : null;
|
|
122
|
-
const section = completed && !caveat ? architectureSection(content, words.architectureHeading) : void 0;
|
|
123
|
-
const silent = section && OPEN_TABLE_CELL.test(section) ? words.silent : null;
|
|
124
|
-
return [selfReported, silent].filter((reason) => reason !== null).map((reason) => ({ file, reason }));
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
// src/plan-format/header.ts
|
|
128
|
-
var DECISION_ROW = /^\|\s*\*?\*?D\d+/gm;
|
|
129
|
-
var readPlanHeader = (markdown, language, fallbackTitle) => {
|
|
130
|
-
const { header } = PLAN_VOCABULARY[language];
|
|
131
|
-
const status = headerField(markdown, header.status);
|
|
132
|
-
return {
|
|
133
|
-
title: firstHeading(markdown, fallbackTitle),
|
|
134
|
-
date: headerField(markdown, header.date),
|
|
135
|
-
status,
|
|
136
|
-
statusWord: classifyStatus(language, status),
|
|
137
|
-
scope: headerField(markdown, header.scope)
|
|
138
|
-
};
|
|
139
|
-
};
|
|
140
|
-
var countDecisions = (markdown) => (markdown.match(DECISION_ROW) ?? []).length;
|
|
141
|
-
|
|
142
95
|
// src/plan-format/lanes.ts
|
|
143
|
-
import { posix } from "path";
|
|
144
96
|
var LANE_NAME = /`([^`]+)`/;
|
|
145
97
|
var CONTINUATION = "\u21B3";
|
|
146
98
|
var PLAN_LINK = /\[([^\]]+)\]\(([^)]+)\)/;
|
|
147
99
|
var PHASE_ID_PREFIX = "P";
|
|
100
|
+
var PHASES_COLUMN = 3;
|
|
148
101
|
var CLAUSE_SEPARATOR = /([;,]|\.(?=\s))\s*/;
|
|
149
102
|
var ENTRY_END = /* @__PURE__ */ new Set([";", "."]);
|
|
150
103
|
var phaseIdPatterns = (language) => {
|
|
@@ -214,6 +167,39 @@ var parseSprintLanes = (markdown, language) => {
|
|
|
214
167
|
};
|
|
215
168
|
var sprintTitleOf = (markdown, fallback) => firstHeading(markdown, fallback);
|
|
216
169
|
var resolvePlanPath = (sprintDirectory, href) => posix.normalize(posix.join(sprintDirectory, href.split("#")[0]));
|
|
170
|
+
var changedOpenLaneRows = (markdown, language, previousLines) => {
|
|
171
|
+
const start = sectionStart(markdown, PLAN_VOCABULARY[language].sections.lanes);
|
|
172
|
+
if (start === -1) return [];
|
|
173
|
+
const closed = new Set(
|
|
174
|
+
parseSprintLanes(markdown, language).filter((lane) => lane.closed).map((lane) => lane.name)
|
|
175
|
+
);
|
|
176
|
+
const rows = [];
|
|
177
|
+
let previous = null;
|
|
178
|
+
for (const { index, line, cells } of firstTableRows(
|
|
179
|
+
sectionLines(markdown, PLAN_VOCABULARY[language].sections.lanes)
|
|
180
|
+
)) {
|
|
181
|
+
const lane = laneNameOf(cells[0] ?? "", previous);
|
|
182
|
+
previous = lane ?? previous;
|
|
183
|
+
if (lane !== null && !closed.has(lane) && !previousLines.has(line))
|
|
184
|
+
rows.push({ line: start + index + 1, lane, phases: cells[PHASES_COLUMN] ?? "" });
|
|
185
|
+
}
|
|
186
|
+
return rows;
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
// src/plan-format/board-prose.ts
|
|
190
|
+
var PHASE_CELL_LIMIT = 250;
|
|
191
|
+
var PROSE_REASON = { long: "long", sideOrder: "side-order" };
|
|
192
|
+
var reasonOf = (phases, marker) => {
|
|
193
|
+
if (phases.includes(marker)) return PROSE_REASON.sideOrder;
|
|
194
|
+
return phases.length > PHASE_CELL_LIMIT ? PROSE_REASON.long : null;
|
|
195
|
+
};
|
|
196
|
+
var boardRowsWithProse = (markdown, language, previousLines) => {
|
|
197
|
+
const { sideOrderMarker } = PLAN_VOCABULARY[language];
|
|
198
|
+
return changedOpenLaneRows(markdown, language, previousLines).flatMap(({ line, lane, phases }) => {
|
|
199
|
+
const reason = reasonOf(phases, sideOrderMarker);
|
|
200
|
+
return reason === null ? [] : [{ line, lane, reason, length: phases.length }];
|
|
201
|
+
});
|
|
202
|
+
};
|
|
217
203
|
|
|
218
204
|
// src/plan-format/phases.ts
|
|
219
205
|
var REVIEW_GATE = /^\s*\*\*review[- ]gate\b/i;
|
|
@@ -223,7 +209,7 @@ var SECTION_HEADING = /^##\s+(.+?)\s*$/;
|
|
|
223
209
|
var TOP_HEADING = /^#\s/;
|
|
224
210
|
var TABLE_STATUS = { done: "\u2705", open: "\u2B1C" };
|
|
225
211
|
var ROW_NUMBER = /^\d+(\.\d+)*[a-z]?$/;
|
|
226
|
-
var NUMBERED_SECTION =
|
|
212
|
+
var NUMBERED_SECTION = new RegExp(`^##\\s+${PHASE_ID_PREFIX}?(\\d+[a-z]?)\\s*[\u2014\u2013:-]\\s*(.*)$`);
|
|
227
213
|
var SECTION_POINTER = /`##\s+([^`]+?)\s*`/;
|
|
228
214
|
var DEFERRED_TO = new RegExp(`\\s\u2192\\s*${PHASE_ID_PREFIX}(\\d+[a-z]?)\\s*$`, "i");
|
|
229
215
|
var PHASE_ARROW = new RegExp(`\\s*\u2192\\s*${PHASE_ID_PREFIX}(\\d+[a-z]?)\\b`, "i");
|
|
@@ -334,6 +320,13 @@ var notePointer = (pointers, phase, line) => {
|
|
|
334
320
|
if (phase && pointer) pointers.set(phase, pointer);
|
|
335
321
|
};
|
|
336
322
|
var headingOf = (line, phaseHeading2) => line.match(phaseHeading2) ?? line.match(NUMBERED_SECTION);
|
|
323
|
+
var phaseHeadings = (markdown, language) => {
|
|
324
|
+
const phaseHeading2 = phaseHeadingPattern(language);
|
|
325
|
+
return markdown.split("\n").flatMap((text, index) => {
|
|
326
|
+
const heading = headingOf(text, phaseHeading2);
|
|
327
|
+
return heading ? [{ line: index + 1, id: (heading[1] ?? "").toLowerCase(), title: terminalSafe(heading[2] ?? "").trim() }] : [];
|
|
328
|
+
});
|
|
329
|
+
};
|
|
337
330
|
var parsePlanPhases = (markdown, language) => {
|
|
338
331
|
const phaseHeading2 = phaseHeadingPattern(language);
|
|
339
332
|
const pointers = /* @__PURE__ */ new Map();
|
|
@@ -355,11 +348,232 @@ var parsePlanPhases = (markdown, language) => {
|
|
|
355
348
|
return withArrivedItems(withPointedSections(phases, pointers, markdown));
|
|
356
349
|
};
|
|
357
350
|
|
|
351
|
+
// src/plan-format/dependencies.ts
|
|
352
|
+
var ID = "(\\d+[a-z]?)";
|
|
353
|
+
var PHASE_ID = new RegExp(`^\\s*${PHASE_ID_PREFIX}?${ID}\\b`, "i");
|
|
354
|
+
var PHASE_RANGE = new RegExp(`^${PHASE_ID_PREFIX}?(\\d+)\\s*[\u2013-]\\s*${PHASE_ID_PREFIX}?(\\d+)$`, "i");
|
|
355
|
+
var BACKTICKED = /`([^`]+)`/g;
|
|
356
|
+
var NO_VALUE = /^[—–-]?$/;
|
|
357
|
+
var TRAILING_NOTE = /\s*\([^)]*\)\s*$/;
|
|
358
|
+
var GLOB_CHARACTER = /[*?{[]/;
|
|
359
|
+
var MAX_RANGE = 50;
|
|
360
|
+
var phaseIdOf = (cell) => PHASE_ID.exec(cell)?.[1]?.toLowerCase() ?? null;
|
|
361
|
+
var splitOutsideParentheses = (cell, separator) => {
|
|
362
|
+
const entries = [];
|
|
363
|
+
let depth = 0;
|
|
364
|
+
let start = 0;
|
|
365
|
+
for (const [index, character] of [...cell].entries()) {
|
|
366
|
+
if (character === "(") depth += 1;
|
|
367
|
+
else if (character === ")") depth = Math.max(depth - 1, 0);
|
|
368
|
+
else if (character === separator && depth === 0) {
|
|
369
|
+
entries.push(cell.slice(start, index));
|
|
370
|
+
start = index + 1;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
return [...entries, cell.slice(start)];
|
|
374
|
+
};
|
|
375
|
+
var rangeOf = (entry) => {
|
|
376
|
+
const match = PHASE_RANGE.exec(entry);
|
|
377
|
+
if (!match) return null;
|
|
378
|
+
const [first, last] = [Number(match[1]), Number(match[2])];
|
|
379
|
+
if (first > last || last - first >= MAX_RANGE) return [];
|
|
380
|
+
return Array.from({ length: last - first + 1 }, (_, offset) => String(first + offset));
|
|
381
|
+
};
|
|
382
|
+
var dependsOf = (cell, language) => {
|
|
383
|
+
const noDependency = PLAN_VOCABULARY[language].noDependency;
|
|
384
|
+
const entries = splitOutsideParentheses(cell, ",").map((entry) => entry.trim()).filter((entry) => !NO_VALUE.test(entry) && !noDependency.includes(entry.toLowerCase()));
|
|
385
|
+
const after = entries.flatMap((entry) => rangeOf(entry) ?? phaseIdOf(entry) ?? []);
|
|
386
|
+
const external = entries.filter((entry) => {
|
|
387
|
+
const spanned = rangeOf(entry);
|
|
388
|
+
return spanned === null ? phaseIdOf(entry) === null : spanned.length === 0;
|
|
389
|
+
});
|
|
390
|
+
return { after, external };
|
|
391
|
+
};
|
|
392
|
+
var pathOf = (entry) => {
|
|
393
|
+
if (/\s/.test(entry)) return null;
|
|
394
|
+
const glob = entry.search(GLOB_CHARACTER);
|
|
395
|
+
const literal = glob === -1 ? entry : entry.slice(0, Math.max(entry.lastIndexOf("/", glob), 0));
|
|
396
|
+
const path = literal.replace(/^\.\//, "").replace(/\/+$/, "");
|
|
397
|
+
return path === "." ? "" : path;
|
|
398
|
+
};
|
|
399
|
+
var likePattern = (language) => {
|
|
400
|
+
const { writesLike, writesLikeJoiners } = PLAN_VOCABULARY[language];
|
|
401
|
+
const words = (list) => list.map(escapeRegExp).join("|");
|
|
402
|
+
const target = `${PHASE_ID_PREFIX}?${ID}`;
|
|
403
|
+
const joiner = `(?:\\s*,\\s*|\\s+(?:${words(writesLikeJoiners)})\\s+|\\s*,\\s*(?:${words(writesLikeJoiners)})\\s+)`;
|
|
404
|
+
return {
|
|
405
|
+
start: new RegExp(`(?<![\\p{L}\\d])(?:${words(writesLike)})\\s+${target}\\b(?:${joiner}${target}\\b)*`, "iu"),
|
|
406
|
+
id: new RegExp(`${PHASE_ID_PREFIX}?${ID}\\b`, "gi")
|
|
407
|
+
};
|
|
408
|
+
};
|
|
409
|
+
var writesLikeOf = (cell, language) => {
|
|
410
|
+
const { start, id } = likePattern(language);
|
|
411
|
+
const found = start.exec(cell)?.[0];
|
|
412
|
+
if (found === void 0) return [];
|
|
413
|
+
const afterWord = found.replace(/^\S+\s+/, "");
|
|
414
|
+
return [...afterWord.matchAll(id)].map((match) => (match[1] ?? "").toLowerCase());
|
|
415
|
+
};
|
|
416
|
+
var writesOf = (cell, language) => ({
|
|
417
|
+
writes: [...cell.matchAll(BACKTICKED)].flatMap((match) => pathOf(match[1] ?? "") ?? []),
|
|
418
|
+
writesLike: writesLikeOf(cell, language)
|
|
419
|
+
});
|
|
420
|
+
var markersOf = (cell, language) => {
|
|
421
|
+
const release = new RegExp(`^${escapeRegExp(PLAN_VOCABULARY[language].releaseMarker)}(?:\\s|$)`, "i");
|
|
422
|
+
return cell.split(";").map((entry) => entry.replace(TRAILING_NOTE, "").trim()).filter((entry) => !NO_VALUE.test(entry) && !release.test(entry));
|
|
423
|
+
};
|
|
424
|
+
var parseDependencyTable = (markdown, language) => {
|
|
425
|
+
const { sections, columns } = PLAN_VOCABULARY[language];
|
|
426
|
+
const { header, rows } = firstTableWithHeader(subsectionLines(markdown, sections.dependencies));
|
|
427
|
+
const [after, writes, marker] = [columns.dependsOn, columns.writes, columns.serialMarker].map(
|
|
428
|
+
(name) => header.indexOf(name)
|
|
429
|
+
);
|
|
430
|
+
if (after === -1 || writes === -1 || marker === -1) return null;
|
|
431
|
+
return rows.flatMap((cells) => {
|
|
432
|
+
const id = phaseIdOf(cells[0] ?? "");
|
|
433
|
+
if (id === null) return [];
|
|
434
|
+
return [
|
|
435
|
+
{
|
|
436
|
+
id,
|
|
437
|
+
...dependsOf(cells[after] ?? "", language),
|
|
438
|
+
...writesOf(cells[writes] ?? "", language),
|
|
439
|
+
serialMarkers: markersOf(cells[marker] ?? "", language)
|
|
440
|
+
}
|
|
441
|
+
];
|
|
442
|
+
});
|
|
443
|
+
};
|
|
444
|
+
var phasesWithoutDependencyRow = (markdown, language) => {
|
|
445
|
+
const table = parseDependencyTable(markdown, language);
|
|
446
|
+
if (table === null) return null;
|
|
447
|
+
const listed = new Set(table.map(({ id }) => id));
|
|
448
|
+
return phaseHeadings(markdown, language).filter(({ id }) => !listed.has(id));
|
|
449
|
+
};
|
|
450
|
+
var mergeSets = (sets) => sets.some((set) => set === null) ? null : sets.flatMap((set) => set ?? []);
|
|
451
|
+
var writeSetOf = (row, byId, chain) => {
|
|
452
|
+
if (chain.includes(row.id)) return null;
|
|
453
|
+
const targets = row.writesLike.map((id) => byId.get(id));
|
|
454
|
+
const like = mergeSets(targets.map((target) => target ? writeSetOf(target, byId, [...chain, row.id]) : null));
|
|
455
|
+
return like === null ? null : [...row.writes, ...like];
|
|
456
|
+
};
|
|
457
|
+
var overlaps = (left, right) => left === "" || right === "" || left === right || left.startsWith(`${right}/`) || right.startsWith(`${left}/`);
|
|
458
|
+
var sharesMarker = (first, second) => {
|
|
459
|
+
const markers = new Set(second.serialMarkers.map((marker) => marker.trim().toLowerCase()));
|
|
460
|
+
return first.serialMarkers.some((marker) => markers.has(marker.trim().toLowerCase()));
|
|
461
|
+
};
|
|
462
|
+
var collides = (first, second) => sharesMarker(first, second) || first.writes.some((path) => second.writes.some((other) => overlaps(path, other)));
|
|
463
|
+
var candidateOf = (row, byId) => {
|
|
464
|
+
const writes = writeSetOf(row, byId, []);
|
|
465
|
+
return writes === null || writes.length === 0 ? [] : [{ id: row.id, writes, serialMarkers: row.serialMarkers }];
|
|
466
|
+
};
|
|
467
|
+
var parallelGroups = (rows, doneIds) => {
|
|
468
|
+
const byId = new Map(rows.map((row) => [row.id, row]));
|
|
469
|
+
const ready = rows.filter((row) => !doneIds.has(row.id) && row.external.length === 0 && row.after.every((id) => doneIds.has(id))).flatMap((row) => candidateOf(row, byId));
|
|
470
|
+
const groups = [];
|
|
471
|
+
for (const candidate of ready) {
|
|
472
|
+
const group = groups.find((members) => members.every((member) => !collides(member, candidate)));
|
|
473
|
+
if (group) group.push(candidate);
|
|
474
|
+
else groups.push([candidate]);
|
|
475
|
+
}
|
|
476
|
+
return groups.map((group) => group.map(({ id }) => id));
|
|
477
|
+
};
|
|
478
|
+
var phasesText = (phases) => phases.map((id) => `${PHASE_ID_PREFIX}${id}`).join(" \u2225 ");
|
|
479
|
+
var donePhaseIds = (markdown, language) => new Set(
|
|
480
|
+
parsePlanPhases(markdown, language).filter(({ items }) => items.length > 0 && items.every((item) => item.done)).map(({ id }) => id)
|
|
481
|
+
);
|
|
482
|
+
var parallelGroupsOfPlan = (markdown, language) => {
|
|
483
|
+
const table = parseDependencyTable(markdown, language);
|
|
484
|
+
return table === null ? null : parallelGroups(table, donePhaseIds(markdown, language));
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
// src/plan-format/documentation-gate.ts
|
|
488
|
+
var DOCUMENTATION_WORDS = Object.values(PLAN_VOCABULARY).map(
|
|
489
|
+
(vocabulary) => vocabulary.sections.documentation.toLowerCase()
|
|
490
|
+
);
|
|
491
|
+
var isDocumentationGateItem = ({ gate, text }) => gate === true && DOCUMENTATION_WORDS.some((word) => text.toLowerCase().startsWith(word));
|
|
492
|
+
var onlyDocumentationOpen = ({ items }) => {
|
|
493
|
+
const open = items.filter((item) => !item.done);
|
|
494
|
+
return open.length === 1 && isDocumentationGateItem(open[0]);
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
// src/plan-format/drift.ts
|
|
498
|
+
var OPEN_TABLE_CELL = /⬜/;
|
|
499
|
+
var DRIFT_WORDS = {
|
|
500
|
+
de: {
|
|
501
|
+
inFlight: /^(Draft|In Progress|Proposed|Open|Planned|Review-Ready|Aktiv)\b/i,
|
|
502
|
+
completion: /\b(done|abgeschlossen|umgesetzt|implementiert|implemented|fertig)\b/i,
|
|
503
|
+
open: /\b(offen|ausstehend|deferred|zurückgestellt)\b/i,
|
|
504
|
+
architecture: /\b(arc42|ADR|Architekturdokumentation|Dokumentation)\b/i,
|
|
505
|
+
architectureHeading: "Architekturdokumentation",
|
|
506
|
+
selfReported: (statusText) => `Status nennt offene Doku-Arbeit: "${statusText.trim()}"`,
|
|
507
|
+
silent: "arc42-Abschlussphase hat offene \u2B1C-Zeilen trotz abgeschlossenem Status"
|
|
508
|
+
},
|
|
509
|
+
en: {
|
|
510
|
+
inFlight: /^(Draft|In Progress|Proposed|Open|Planned|Review-Ready|Active)\b/i,
|
|
511
|
+
completion: /\b(done|completed|implemented|finished)\b/i,
|
|
512
|
+
open: /\b(open|outstanding|pending|deferred)\b/i,
|
|
513
|
+
architecture: /\b(arc42|ADR|architecture documentation|documentation)\b/i,
|
|
514
|
+
architectureHeading: "Architecture documentation",
|
|
515
|
+
selfReported: (statusText) => `Status names open documentation work: "${statusText.trim()}"`,
|
|
516
|
+
silent: "The arc42 closing phase has open \u2B1C rows despite a completed status"
|
|
517
|
+
}
|
|
518
|
+
};
|
|
519
|
+
var statusLine = (content, name) => content.match(new RegExp(`^\\*\\*${escapeRegExp(name)}:\\*\\*\\s*(.+)$`, "m"))?.[1];
|
|
520
|
+
var architectureSection = (content, heading) => content.match(new RegExp(`#{2,4}[^\\n]*${escapeRegExp(heading)}[^\\n]*\\n([\\s\\S]*?)(?=\\n#{1,4} |$)`))?.[1];
|
|
521
|
+
var findDrift = (file, content, language) => {
|
|
522
|
+
const statusText = statusLine(content, PLAN_VOCABULARY[language].header.status);
|
|
523
|
+
const words = DRIFT_WORDS[language];
|
|
524
|
+
if (!statusText || words.inFlight.test(statusText.trim())) return [];
|
|
525
|
+
const completed = words.completion.test(statusText);
|
|
526
|
+
const caveat = words.open.test(statusText);
|
|
527
|
+
const selfReported = completed && caveat && words.architecture.test(statusText) ? words.selfReported(statusText) : null;
|
|
528
|
+
const section = completed && !caveat ? architectureSection(content, words.architectureHeading) : void 0;
|
|
529
|
+
const silent = section && OPEN_TABLE_CELL.test(section) ? words.silent : null;
|
|
530
|
+
return [selfReported, silent].filter((reason) => reason !== null).map((reason) => ({ file, reason }));
|
|
531
|
+
};
|
|
532
|
+
|
|
533
|
+
// src/plan-format/header.ts
|
|
534
|
+
var DECISION_ROW = /^\|\s*\*?\*?D\d+/gm;
|
|
535
|
+
var readPlanHeader = (markdown, language, fallbackTitle) => {
|
|
536
|
+
const { header } = PLAN_VOCABULARY[language];
|
|
537
|
+
const status = headerField(markdown, header.status);
|
|
538
|
+
return {
|
|
539
|
+
title: firstHeading(markdown, fallbackTitle),
|
|
540
|
+
date: headerField(markdown, header.date),
|
|
541
|
+
status,
|
|
542
|
+
statusWord: classifyStatus(language, status),
|
|
543
|
+
scope: headerField(markdown, header.scope)
|
|
544
|
+
};
|
|
545
|
+
};
|
|
546
|
+
var countDecisions = (markdown) => (markdown.match(DECISION_ROW) ?? []).length;
|
|
547
|
+
|
|
548
|
+
// src/plan-format/model-column.ts
|
|
549
|
+
var NOTE = /\([^)]*\)/g;
|
|
550
|
+
var ENTRY_SEPARATOR = /[/,+→]/;
|
|
551
|
+
var TOKEN_SEPARATOR = /[^a-z0-9]+/;
|
|
552
|
+
var NAME = /^[a-z]+$/;
|
|
553
|
+
var NUMBER = /^\d+$/;
|
|
554
|
+
var tokensOf = (text) => text.toLowerCase().split(TOKEN_SEPARATOR);
|
|
555
|
+
var modelOf = (entry) => {
|
|
556
|
+
const tokens = tokensOf(entry);
|
|
557
|
+
const names = tokens.filter((token) => NAME.test(token));
|
|
558
|
+
return names.length > 0 ? { names, version: tokens.filter((token) => NUMBER.test(token)) } : null;
|
|
559
|
+
};
|
|
560
|
+
var columnModels = (column) => column.replace(NOTE, "").split(ENTRY_SEPARATOR).flatMap((entry) => modelOf(entry) ?? []);
|
|
561
|
+
var startsWith = (tokens, prefix) => prefix.every((part, index) => tokens[index] === part);
|
|
562
|
+
var fitsName = (tokens, name, version) => tokens.some(
|
|
563
|
+
(token, index) => token === name && (startsWith(tokens.slice(index + 1), version) || startsWith(tokens.slice(index - version.length, index), version))
|
|
564
|
+
);
|
|
565
|
+
var fits = (tokens, { names, version }) => names.some((name) => fitsName(tokens, name, version));
|
|
566
|
+
var differsFromColumn = (column, modelId) => {
|
|
567
|
+
const models = columnModels(column);
|
|
568
|
+
const tokens = tokensOf(modelId);
|
|
569
|
+
return models.length > 0 && !models.some((model) => fits(tokens, model));
|
|
570
|
+
};
|
|
571
|
+
|
|
358
572
|
// src/plan-format/phase-tally.ts
|
|
359
573
|
var DONE_MARK = /^\s*[-*]\s\[x\]|\|\s*✅\s*\|/gim;
|
|
360
574
|
var OPEN_MARK = /^\s*[-*]\s\[ \]|\|\s*⬜\s*\|/gm;
|
|
361
575
|
var TRAILING_SEPARATOR = /[:—–-]\s*$/;
|
|
362
|
-
var
|
|
576
|
+
var PHASE_ID2 = /^(\d+[a-z]?)/;
|
|
363
577
|
var modelFor = (markdown, phaseWord, phaseTitle) => {
|
|
364
578
|
const prefix = new RegExp(`^${escapeRegExp(phaseWord)}\\s*`, "i");
|
|
365
579
|
const number = /^(\d+)/.exec(phaseTitle.replace(prefix, ""))?.[1];
|
|
@@ -371,7 +585,7 @@ var sectionOf = (section, heading, markdown, phaseWord) => {
|
|
|
371
585
|
const match = heading.exec(section);
|
|
372
586
|
if (!match) return null;
|
|
373
587
|
const title = match[1].replace(TRAILING_SEPARATOR, "").trim();
|
|
374
|
-
const phaseId =
|
|
588
|
+
const phaseId = PHASE_ID2.exec(title)?.[1]?.toLowerCase() ?? "";
|
|
375
589
|
const targets = section.split("\n").flatMap((line) => deferralTarget(line, phaseId) ?? []);
|
|
376
590
|
const done = (section.match(DONE_MARK) ?? []).length;
|
|
377
591
|
const open = (section.match(OPEN_MARK) ?? []).length - targets.length;
|
|
@@ -436,45 +650,18 @@ var mergedPhasesWithOpenBoxes = (markdown, base, language) => {
|
|
|
436
650
|
};
|
|
437
651
|
return phaseSections(markdown, language).filter(({ id, openLines }) => openLines.length > 0 && isMerged(id)).map(({ line, id, title, openLines }) => ({ line, id, title, openLines }));
|
|
438
652
|
};
|
|
439
|
-
var boardRowsWithoutPhase = (markdown, language, previousLines) =>
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
const closed = new Set(
|
|
443
|
-
parseSprintLanes(markdown, language).filter((lane) => lane.closed).map((lane) => lane.name)
|
|
444
|
-
);
|
|
445
|
-
const rows = [];
|
|
446
|
-
let previous = null;
|
|
447
|
-
for (const { index, line, cells } of firstTableRows(
|
|
448
|
-
sectionLines(markdown, PLAN_VOCABULARY[language].sections.lanes)
|
|
449
|
-
)) {
|
|
450
|
-
const [name = "", , , phases = ""] = cells;
|
|
451
|
-
const lane = laneNameOf(name, previous);
|
|
452
|
-
previous = lane ?? previous;
|
|
453
|
-
const counts = lane !== null && !closed.has(lane) && !previousLines.has(line);
|
|
454
|
-
if (counts && phaseIdsOfCell(phases, language).length === 0) rows.push({ line: start + index + 1, lane, phases });
|
|
455
|
-
}
|
|
456
|
-
return rows;
|
|
457
|
-
};
|
|
653
|
+
var boardRowsWithoutPhase = (markdown, language, previousLines) => changedOpenLaneRows(markdown, language, previousLines).filter(
|
|
654
|
+
({ phases }) => phaseIdsOfCell(phases, language).length === 0
|
|
655
|
+
);
|
|
458
656
|
|
|
459
657
|
// src/plan-format/progress.ts
|
|
460
658
|
var sumPhases = (phases) => ({
|
|
461
659
|
done: phases.reduce((sum, phase) => sum + phase.done, 0),
|
|
462
660
|
total: phases.reduce((sum, phase) => sum + phase.total, 0)
|
|
463
661
|
});
|
|
464
|
-
var
|
|
465
|
-
var findPhase = (phases, id) => phases.find((phase) => phase.id === id) ?? phases.find((phase) => phase.id === baseId(id));
|
|
466
|
-
var keptPhaseIds = (phases, ids) => {
|
|
467
|
-
const shown = new Set(ids.flatMap((id) => phases.filter((phase) => phase.id === id)));
|
|
468
|
-
return ids.filter((id) => {
|
|
469
|
-
const phase = findPhase(phases, id);
|
|
470
|
-
if (!phase || phase.id === id) return true;
|
|
471
|
-
if (shown.has(phase)) return false;
|
|
472
|
-
shown.add(phase);
|
|
473
|
-
return true;
|
|
474
|
-
});
|
|
475
|
-
};
|
|
662
|
+
var phaseById = (phases, id) => phases.find((phase) => phase.id === id);
|
|
476
663
|
var phaseProgress = (phases, id) => {
|
|
477
|
-
const phase =
|
|
664
|
+
const phase = phaseById(phases, id);
|
|
478
665
|
const items = phase?.items ?? [];
|
|
479
666
|
return {
|
|
480
667
|
id,
|
|
@@ -499,12 +686,11 @@ var newestStepWith = (steps, pick) => {
|
|
|
499
686
|
}
|
|
500
687
|
return null;
|
|
501
688
|
};
|
|
502
|
-
var shownPhase = (phases, id) => phases.find((phase) => phase.id === id) ?? phases.find((phase) => phase.id === baseId(id));
|
|
503
689
|
var earlierPhase = (steps, before, id) => {
|
|
504
690
|
const planHref = steps[before].step.planHref;
|
|
505
691
|
for (let stepIndex = before - 1; stepIndex >= 0; stepIndex--) {
|
|
506
692
|
if (steps[stepIndex].step.planHref !== planHref) continue;
|
|
507
|
-
const phase =
|
|
693
|
+
const phase = phaseById(steps[stepIndex].phases, id);
|
|
508
694
|
if (phase) return { phase, current: { stepIndex, phaseId: phase.id } };
|
|
509
695
|
}
|
|
510
696
|
return null;
|
|
@@ -539,14 +725,14 @@ var currentPhaseOf = (steps) => newestRowPhase(steps) ?? phaseFromTicks(steps);
|
|
|
539
725
|
var laneProgress = (lane, plans) => {
|
|
540
726
|
const steps = lane.steps.map((step) => {
|
|
541
727
|
const phases = step.planHref ? plans[step.planHref] : void 0;
|
|
542
|
-
return { step, phases: phases ?
|
|
728
|
+
return { step, phases: phases ? step.phaseIds.map((id) => phaseProgress(phases, id)) : [] };
|
|
543
729
|
});
|
|
544
730
|
return { lane, steps, current: currentPhaseOf(steps), ...sumPhases(steps.flatMap((step) => step.phases)) };
|
|
545
731
|
};
|
|
546
732
|
var currentPhase = (progress) => {
|
|
547
733
|
if (!progress.current) return null;
|
|
548
734
|
const { stepIndex, phaseId } = progress.current;
|
|
549
|
-
return progress.steps[stepIndex].phases
|
|
735
|
+
return phaseById(progress.steps[stepIndex].phases, phaseId) ?? null;
|
|
550
736
|
};
|
|
551
737
|
var activeItem = (phase) => phase.items.find((item) => !item.done) ?? null;
|
|
552
738
|
var shortTitle = (progress) => {
|
|
@@ -595,40 +781,53 @@ var readPhaseDependencies = (planMarkdown, language) => {
|
|
|
595
781
|
};
|
|
596
782
|
export {
|
|
597
783
|
ADDRESS_FORM,
|
|
784
|
+
APPROVAL_TEXTS,
|
|
598
785
|
LANGUAGE_NAME,
|
|
786
|
+
PHASE_CELL_LIMIT,
|
|
599
787
|
PHASE_ID_PREFIX,
|
|
600
788
|
PLAN_LANGUAGE,
|
|
601
789
|
PLAN_STATUS,
|
|
602
790
|
PLAN_VOCABULARY,
|
|
791
|
+
PROSE_REASON,
|
|
603
792
|
RESPONSE_ADDRESS,
|
|
604
793
|
SELF_MERGE_LABEL,
|
|
605
794
|
activeItem,
|
|
795
|
+
boardRowsWithProse,
|
|
606
796
|
boardRowsWithoutPhase,
|
|
607
797
|
classifyStatus,
|
|
798
|
+
columnModels,
|
|
608
799
|
countDecisions,
|
|
609
800
|
currentPhase,
|
|
610
801
|
currentPhaseOf,
|
|
611
802
|
deferralTarget,
|
|
803
|
+
differsFromColumn,
|
|
612
804
|
endsPhase,
|
|
613
805
|
findDrift,
|
|
614
806
|
hasLanesTable,
|
|
615
807
|
isCheckboxLine,
|
|
808
|
+
isDocumentationGateItem,
|
|
616
809
|
isOpenCheckboxLine,
|
|
617
810
|
isPlanLanguage,
|
|
618
811
|
isReviewGateLine,
|
|
619
812
|
isTickedUnmerged,
|
|
620
|
-
keptPhaseIds,
|
|
621
813
|
laneNameOf,
|
|
622
814
|
laneProgress,
|
|
623
815
|
mergedPhasesWithOpenBoxes,
|
|
624
816
|
misplacedDeferrals,
|
|
625
817
|
newPhasesWithoutReviewGate,
|
|
818
|
+
onlyDocumentationOpen,
|
|
819
|
+
parallelGroups,
|
|
820
|
+
parallelGroupsOfPlan,
|
|
821
|
+
parseDependencyTable,
|
|
626
822
|
parsePlanPhases,
|
|
627
823
|
parseSprintLanes,
|
|
628
824
|
phaseHeading,
|
|
629
825
|
phaseHeadingPattern,
|
|
826
|
+
phaseHeadings,
|
|
630
827
|
phaseIdsOfCell,
|
|
631
828
|
phaseIdsOfPlan,
|
|
829
|
+
phasesText,
|
|
830
|
+
phasesWithoutDependencyRow,
|
|
632
831
|
readPhaseDependencies,
|
|
633
832
|
readPlanHeader,
|
|
634
833
|
readSprintDocument,
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="960" height="440" viewBox="0 0 960 440" role="img" aria-labelledby="t d"><title id="t">What runs where</title><desc id="d">The coordinator, running /jh-orchestrate in the main checkout, prompts two lanes, each in its own git worktree, and asks you the open decisions as a choice, with one approval per pull request. The lanes send their questions back as text. jh watch reads each lane's state, pull request and checks and wakes the coordinator. Small dots travel along the lines to show the messages.</desc><defs><style>.s{font-family:ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif}.m{font-family:ui-monospace, SFMono-Regular, Menlo, Consolas, 'Liberation Mono', monospace}@keyframes k1{0%{opacity:0;transform:translate(388px,182px)}2.22%{opacity:0;transform:translate(388px,182px)}2.56%{opacity:1;transform:translate(388px,182px)}3.89%{opacity:1;transform:translate(420.4px,173px)}5.56%{opacity:1;transform:translate(451.7px,162.1px)}7.22%{opacity:1;transform:translate(482.1px,150.1px)}8.89%{opacity:1;transform:translate(512px,137.2px)}10.56%{opacity:1;transform:translate(541.5px,124px)}12.22%{opacity:1;transform:translate(571px,111px)}13.89%{opacity:1;transform:translate(600.6px,98.7px)}15.56%{opacity:1;transform:translate(630.7px,87.5px)}17.22%{opacity:1;transform:translate(661.4px,77.9px)}18.89%{opacity:1;transform:translate(693px,70.5px)}20.56%{opacity:1;transform:translate(725.8px,65.7px)}22.22%{opacity:0;transform:translate(760px,64px)}100%{opacity:0;transform:translate(760px,64px)}}.k1{animation:k1 9s linear infinite both}@keyframes k2{0%{opacity:0;transform:translate(388px,226px)}8.89%{opacity:0;transform:translate(388px,226px)}9.22%{opacity:1;transform:translate(388px,226px)}10.56%{opacity:1;transform:translate(420.4px,235.1px)}12.22%{opacity:1;transform:translate(451.7px,246px)}13.89%{opacity:1;transform:translate(482.1px,258.2px)}15.56%{opacity:1;transform:translate(512px,271.3px)}17.22%{opacity:1;transform:translate(541.5px,284.8px)}18.89%{opacity:1;transform:translate(571px,298px)}20.56%{opacity:1;transform:translate(600.6px,310.6px)}22.22%{opacity:1;transform:translate(630.7px,322px)}23.89%{opacity:1;transform:translate(661.4px,331.8px)}25.56%{opacity:1;transform:translate(693px,339.3px)}27.22%{opacity:1;transform:translate(725.8px,344.3px)}28.89%{opacity:0;transform:translate(760px,346px)}100%{opacity:0;transform:translate(760px,346px)}}.k2{animation:k2 9s linear infinite both}@keyframes k3{0%{opacity:0;transform:translate(770px,94px)}24.44%{opacity:0;transform:translate(770px,94px)}24.78%{opacity:1;transform:translate(770px,94px)}25.56%{opacity:1;transform:translate(762.1px,107.5px)}26.67%{opacity:1;transform:translate(753.5px,119.9px)}27.78%{opacity:1;transform:translate(744.3px,131.3px)}28.89%{opacity:1;transform:translate(734.7px,141.7px)}30%{opacity:1;transform:translate(724.7px,151.1px)}31.11%{opacity:1;transform:translate(714.5px,159.6px)}32.22%{opacity:1;transform:translate(704.2px,167.2px)}33.33%{opacity:1;transform:translate(694px,174px)}34.44%{opacity:1;transform:translate(683.9px,179.9px)}35.56%{opacity:1;transform:translate(674.2px,185px)}36.67%{opacity:1;transform:translate(664.8px,189.4px)}37.78%{opacity:0;transform:translate(656px,193px)}100%{opacity:0;transform:translate(656px,193px)}}.k3{animation:k3 9s linear infinite both}@keyframes k4{0%{opacity:0;transform:translate(770px,316px)}28.89%{opacity:0;transform:translate(770px,316px)}29.22%{opacity:1;transform:translate(770px,316px)}30%{opacity:1;transform:translate(762.1px,302.6px)}31.11%{opacity:1;transform:translate(753.5px,290.3px)}32.22%{opacity:1;transform:translate(744.3px,279.1px)}33.33%{opacity:1;transform:translate(734.7px,268.9px)}34.44%{opacity:1;transform:translate(724.7px,259.7px)}35.56%{opacity:1;transform:translate(714.5px,251.5px)}36.67%{opacity:1;transform:translate(704.2px,244.2px)}37.78%{opacity:1;transform:translate(694px,237.8px)}38.89%{opacity:1;transform:translate(683.9px,232.2px)}40%{opacity:1;transform:translate(674.2px,227.4px)}41.11%{opacity:1;transform:translate(664.8px,223.3px)}42.22%{opacity:0;transform:translate(656px,220px)}100%{opacity:0;transform:translate(656px,220px)}}.k4{animation:k4 9s linear infinite both}@keyframes k5{0%{opacity:0;transform:translate(600px,205px)}42.22%{opacity:0;transform:translate(600px,205px)}42.56%{opacity:1;transform:translate(600px,205px)}43.24%{opacity:1;transform:translate(584.4px,205px)}44.26%{opacity:1;transform:translate(567.8px,205px)}45.28%{opacity:1;transform:translate(550.3px,205px)}46.3%{opacity:1;transform:translate(532.1px,205px)}47.31%{opacity:1;transform:translate(513.6px,205px)}48.33%{opacity:1;transform:translate(494.8px,205px)}49.35%{opacity:1;transform:translate(475.9px,205px)}50.37%{opacity:1;transform:translate(457.2px,205px)}51.39%{opacity:1;transform:translate(438.8px,205px)}52.41%{opacity:1;transform:translate(421.1px,205px)}53.43%{opacity:1;transform:translate(404px,205px)}54.44%{opacity:0;transform:translate(388px,205px)}100%{opacity:0;transform:translate(388px,205px)}}.k5{animation:k5 9s linear infinite both}@keyframes k6{0%{opacity:0;transform:translate(760px,346px)}48.89%{opacity:0;transform:translate(760px,346px)}49.22%{opacity:1;transform:translate(760px,346px)}50.37%{opacity:1;transform:translate(725.8px,344.2px)}51.85%{opacity:1;transform:translate(693px,339.3px)}53.33%{opacity:1;transform:translate(661.4px,331.8px)}54.81%{opacity:1;transform:translate(630.7px,322px)}56.3%{opacity:1;transform:translate(600.6px,310.6px)}57.78%{opacity:1;transform:translate(571px,298px)}59.26%{opacity:1;transform:translate(541.5px,284.8px)}60.74%{opacity:1;transform:translate(512px,271.3px)}62.22%{opacity:1;transform:translate(482.1px,258.2px)}63.7%{opacity:1;transform:translate(451.7px,246px)}65.19%{opacity:1;transform:translate(420.4px,235.1px)}66.67%{opacity:0;transform:translate(388px,226px)}100%{opacity:0;transform:translate(388px,226px)}}.k6{animation:k6 9s linear infinite both}@keyframes k7{0%{opacity:0;transform:translate(300px,204px)}67.78%{opacity:0;transform:translate(300px,204px)}68.11%{opacity:1;transform:translate(300px,204px)}68.7%{opacity:1;transform:translate(287.1px,202.6px)}69.63%{opacity:1;transform:translate(273.5px,201.5px)}70.56%{opacity:1;transform:translate(259.3px,200.6px)}71.48%{opacity:1;transform:translate(244.6px,200px)}72.41%{opacity:1;transform:translate(229.5px,199.6px)}73.33%{opacity:1;transform:translate(214.2px,199.5px)}74.26%{opacity:1;transform:translate(198.8px,199.6px)}75.19%{opacity:1;transform:translate(183.4px,200px)}76.11%{opacity:1;transform:translate(168.1px,200.6px)}77.04%{opacity:1;transform:translate(153px,201.5px)}77.96%{opacity:1;transform:translate(138.3px,202.6px)}78.89%{opacity:0;transform:translate(124px,204px)}100%{opacity:0;transform:translate(124px,204px)}}.k7{animation:k7 9s linear infinite both}@keyframes k8{0%{opacity:0;transform:translate(124px,204px)}83.33%{opacity:0;transform:translate(124px,204px)}83.67%{opacity:1;transform:translate(124px,204px)}84.26%{opacity:1;transform:translate(138.3px,202.6px)}85.19%{opacity:1;transform:translate(153px,201.5px)}86.11%{opacity:1;transform:translate(168.1px,200.6px)}87.04%{opacity:1;transform:translate(183.4px,200px)}87.96%{opacity:1;transform:translate(198.8px,199.6px)}88.89%{opacity:1;transform:translate(214.2px,199.5px)}89.81%{opacity:1;transform:translate(229.5px,199.6px)}90.74%{opacity:1;transform:translate(244.6px,200px)}91.67%{opacity:1;transform:translate(259.3px,200.6px)}92.59%{opacity:1;transform:translate(273.5px,201.5px)}93.52%{opacity:1;transform:translate(287.1px,202.6px)}94.44%{opacity:0;transform:translate(300px,204px)}100%{opacity:0;transform:translate(300px,204px)}}.k8{animation:k8 9s linear infinite both}@media (prefers-reduced-motion:reduce){*{animation:none!important}}</style><clipPath id="jack-clip"><rect width="240" height="240" rx="50"/></clipPath><symbol id="jack" viewBox="0 0 240 240"><rect width="240" height="240" rx="50" fill="#212E34"/><g clip-path="url(#jack-clip)"><g transform="translate(34 8)"><path fill="#4A4E51" d="M80 50.5c.2 .1 .2 .1 1 1c-.2 .6-.3 1.3-.5 2c-.7 .4-.7 .4-4 2c-6 3.3-6 3.3-7 6.5c1.2-.4 2.3-.6 3.5-1c.8 0 1.7 0 2.5 0c.2 .1 .2 .1 1 1c-.5 2-.5 2-5.5 6.5c-3 5-3 5-5.5 11c.2 .3 .2 .3 1 2c.2 .3 .3 .7 .5 1c-.7 .7-1.3 1.3-2 2c-1.3-.2-2.6-.3-4-.5c0-1.2 0-2.3 0-3.5c-.2 .1-.2 .1-1.5 .5c-1.2 2.7-2.4 5.3-3.5 8c-1 2.1-2 4.2-3 6.3c-2.5 5.7-2.5 5.7-3 8.2c.5 0 1 0 1.5 0c.1-.8 .4-1.7 .5-2.5c.6 0 1.3 0 2 0c.5 2 1 3.9 1.6 6c1 4 2.2 8 3.4 11.9c2.1 8.4 1.1 15.5-3 23.1c-.6-.2-1.3-.3-2-.5c-.1-1.5-.3-3-.5-4.6c-.3-2.6-.5-5.2-1-7.9c-.5 0-1 0-1.5 0c-.1 1.7-.4 3.3-.5 5c-2.7 10.2-2.7 10.2-6 13.5c-6.1-3.1-9-14.9-11-21c-1.1-8.2 .9-14.7 3.5-22.5c-8.4 8.3-8.3 20.5-9 31.5c-.6-.2-1.3-.3-2-.5c-5.4-8.9-5.1-15-3-25c.5-1 1-2 1.5-3c-3 1.5-3 1.5-6.5 5c-.4 0-.6 0-1 0c.8-6.5 3.2-10.2 7.5-15c.6-.8 .6-.8 3.5-5c2.6-2.6 5.2-5.1 8-7.5c.6-.6 .6-.6 3.5-3.5c-.5-2-.5-2-1-3.5c3.6-6 6.5-9.2 12.5-13c.4-.6 .6-1.3 1-2c-.5-.6-1-1.3-1.5-2c2-3 2-3 4.5-4.5c.5 1 .5 1 1 3.5c2.5-1 2.5-1 8.5-5.5c.3 0 .7 0 1 0c-.1-.4-.1-.4-.5-2.5c4.3-3.3 10-2.5 15-1zM153 69.5c1.2 .2 2.3 .3 3.5 .5c.3 .7 .7 1.3 1 2c.3 0 .7 0 1 0c.5 1.2 1 2.3 1.5 3.5c3.4 7.6 6.2 13.7 12.2 19.6c3.3 3.4 3.3 3.4 3.8 5.9c-.2 .2-.3 .3-.5 .5c.1 .7 .1 .7 .5 4.5c-.5 .3-1 .7-1.5 1c-.8-.3-1.7-.7-2.5-1c.3 .7 .7 1.3 1 2c1.2 5.8 1.4 10 1 16c-.7 0-1.3 0-2 0c-.4-1.3-.8-2.7-1.3-4.1c-2.1-6.1-5.2-11.4-8.7-16.9c-.2-.8-.2-.8-1.5-5c-.3-.6-.3-.6-2.1-3.5c-1.9-3.5-1.9-3.5-2.9-8.5c.7 0 1.3 0 2 0c.5 .5 1 1 1.5 1.5c-.8-2.3-1.7-4.6-2.5-7c-.7-2-1.3-4-2-6c-.7-1-1.3-2-2-3c.2-.7 .3-1.3 .5-2zM86.5 100.5c1 0 2 0 3 0c1.3 .8 2.7 1.7 4 2.5c-2.5 2.5-2.5 2.5-8 5.5c-5.5 4.3-8.6 7-10 14c-.8 2.2-1.7 4.3-2.5 6.5c-1.2 4.6-.5 9.3 0 14c-.5 0-1 0-1.5 0c-.2 2-.3 3.9-.5 6c-.7 0-1.3 0-2 0c-.3-.6-.3-.6-2-3.5c-1-.8-2-1.7-3-2.5c-.5-1.5-.5-1.5 1.5-8.5c.3-10.3-1-18.2-4-28c0-4 0-4 1-6c2 2 2 2 3 6.5c2.5-.5 2.5-.5 8-4c9.8-3.5 9.8-3.5 13-2.5zM43 161.5c2.5 2.5 2.5 2.5 3 5c-1.5 2.5-1.5 2.5-4 5c.5 3.5 .5 3.5 4 6.5c-.1 1.2-.4 2.3-.5 3.5c-5.3 3.5-5.3 3.5-8.5 3.5c-6-2.5-6-2.5-11.5-7c0-3 0-3 4-8c9.5-8.5 9.5-8.5 13.5-8.5z"/><path fill="#9B9D9E" d="M142.5 60.5c.5 1 .5 1-.5 4.5c1.2 1.5 2.3 3 3.5 4.5c2 4.5 2 4.5 2.5 9.5c-.5 .2-1 .3-1.5 .5c-.1 .2-.1 .2-.5 1c-2-1-3.9-2-6-3c-.8-.3-1.7-.7-2.5-1c-.3-.7-.7-1.3-1-2c.3-.5 .7-1 1-1.5c-.2-.7-.3-1.3-.5-2c-3.5 .5-3.5 .5-7.5 4c-.5 3-.5 3 0 5.5c-2 4-2 4-3.5 5.5c-.3 0-.7 0-1 0c0 .7 0 1.3 0 2c-2-1-2-1-2.5-2.5c.3-13.7 .3-13.7 4.8-18.5c4.1-3.9 6.3-4.8 11.7-6c1-.5 1-.5 3.5-.5zM98 70.5c.5 .3 1 .7 1.5 1c1.3 .3 2.7 .7 4 1c4.4 5.8 6.5 10.3 6.5 17.5c-2 2-2 2-5 2.5c-.3 .2-.3 .2-2 1c-.2-.2-.2-.2-1.5-1c.5-3 .5-3 1-5c-.7-.8-1.3-1.7-2-2.5c-.2-1.7-.3-3.3-.5-5c-4.5-2.7-8.5-3-13.5-2.5c-.1 .3-.1 .3-.5 1.5c1.3 .8 2.7 1.7 4 2.5c-.1 .4-.1 .4-.5 2.5c-2.3 1.2-4.6 2.3-7 3.5c-5.5 5.5-5.5 5.5-6.5 7c0 .5 0 1 0 1.5c.9-.1 .9-.1 5.5-.5c1 1.3 2 2.7 3 4c-.2 .5-.3 1-.5 1.5c-1.7 .5-3.2 1-5 1.6c-5.5 1.9-5.5 1.9-11 5.4c-.4 .1-.4 .1-2.5 .5c-1.5-1.5-1.5-1.5-2.5-6c-.4-.3-.6-.7-1-1c0-6.7 .9-11.7 3-18c.2-.6 .2-.6 1-3.5c7.8-9.7 20.5-14.1 32-9.5z"/><path fill="#FAF9F8" d="M103.5 40c0 .4 0 .6 0 1c2.2-.1 4.3-.4 6.5-.5c7 0 7 0 9.5 1c-.2 .6-.3 1.3-.5 2c-1.8 .6-3.7 1.3-5.5 2c-.2 .5-.3 1-.5 1.5c.7-.1 1.3-.4 2-.5c13.4-.3 13.4-.3 19.5 3.5c-.3 .6-.7 1.3-1 2c-1 0-2 0-3 0c0 .5 0 1 0 1.5c1.2 .1 1.2 .1 7 .5c3.5 2 3.5 2 5.5 4.5c0 1 0 2 0 3c-3.3 .9-6.7 1.6-10 2.5c-7.5 5-7.5 5-8.5 9c-.3 4.5-.4 9-.5 13.5c1.5 1 1.5 1 7.5 2.5c.9 .6 .9 .6 5.5 3.5c2 1 2 1 9 2.5c7.9 4 11.3 8.3 15 16.5c0 .5 0 1 0 1.5c-1 .2-2 .3-3 .5c.5 1.8 1 3.7 1.5 5.5c.3 8.5-.8 13.6-6 20.5c-.7-.2-1.3-.3-2-.5c0-1.7 0-3.3 0-5c-.7 .2-1.3 .3-2 .5c-1.3 2.7-2.7 5.3-4 8c-3.2 3.7-6.4 6.2-11 8c-1-1-1-1-1.5-3c-2.5 1.3-4.8 2.9-7 4.5c-6.3 2.8-10.8 3.2-17.6 1c-1.3-.5-2.6-1-3.9-1.5c.2 1.2 .3 2.3 .5 3.5c-.2 .3-.3 .7-.5 1c-5.9 0-9-1.8-14-5c-3-3.5-3-3.5-3-4.5c-.7 0-1.3 0-2 0c.5 2.2 1 4.3 1.5 6.5c-.3 .3-.7 .7-1 1c-4.5-.9-7.6-3.6-11-6.5c-4-6-4-9.3-4-16.5c2.5-7.5 2.5-7.5 3.5-9.5c.3-1.7 .7-3.3 1-5c6.4-8 14.7-13 24.5-16c6.4-6.3 6.4-6.3 8.5-10.5c0-6-1.6-9.8-4.5-15c-.2-.5-.3-1-.5-1.5c-1.7-.3-3.3-.7-5-1c-1.3-.7-2.7-1.3-4-2c-5.7-1.9-10.2-.3-15.5 1.5c-10 5.5-10 5.5-13 10c-.3-.2-.3-.2-2-1c1.3-6.8 4.6-11.7 9.5-16.5c.5-.3 1-.6 1.5-1c.2-.5 .3-1 .5-1.5c-1 .1-1 .1-6 1c-.3-.4-.7-.6-1-1c.5-2 .5-2 4-6c5-2.5 5-2.5 7.5-3c0-.5 0-1 0-1.5c-10.5-1.5-18.5 1-27 7c-.5 .9-1 1.6-1.5 2.5c-.9 .6-.9 .6-5.5 4c-4 3.5-7.6 7-11 11c-4.6 3.8-8.7 6.8-14 9.5c-.7-.3-1.3-.7-2-1c.2-.7 .3-1.3 .5-2c1.8-1 3.7-2 5.5-3c5.2-4.2 9.7-8.5 14-13.5c3.1-2.8 6.2-5.5 9.5-8c1.3-1.3 2.6-2.6 4-4c5.6-3.6 10.4-6.1 17-7c.5-1 1-2 1.5-3c7.2-2.4 12.5-2.2 20-.5c.2 .1 .2 .1 1 .5c3.5 0 3.5 0 8.5-3.5c1-.5 1-.5 3-.5zM118 92.5c-1 1.2-2 2.3-3 3.5c.5 2.5 .5 2.5 3 5c1.8 .8 3.7 1.7 5.5 2.5c.5 1 .5 1 .5 4.5c-4 4.8-6.1 5-12.5 6c0 .7 0 1.3 0 2c4.7 1.5 7.7 .2 12-1.5c1.2-.7 2.3-1.3 3.5-2c1.3 .3 2.7 .7 4 1c3.5 0 3.5 0 5.5-1.5c-.2-.5-.3-1-.5-1.5c-1.7-.3-3.3-.7-5-1c-2.5-2-2.5-2-3-5.5c2-3 2-3 4-5.5c.5-3.5 .5-3.5-1.5-6.5c-4.3-2.2-8.2-1-12.5 .5zM146 58.5c5.8 1.1 8.3 3.9 12.5 8c3.7 5.9 4.4 7.3 5 14c2.8 6.2 5.4 10 10.5 14.5c5.2 5.2 7.2 10.3 9 17.5c0 3.5 0 3.5-2 11c-.2 1.3-.2 1.3-1 7.5c-3.8 10.7-3.8 10.7-8.5 13.5c-.5 .2-.5 .2-3 1c-1 1.3-2 2.7-3 4c-4.5 3-4.5 3-10.5 5c-2.7-.6-5.3-1.3-8-2c-1.7 1.2-3.3 2.3-5 3.5c-2 1-2 1-4 .5c.5-1.5 .5-1.5 6-5c.3-.3 .3-.3 2-2c2.4 0 4.9 0 7.5 0c.5 .5 1 1 1.5 1.5c4.3-.6 7.1-2.3 10.5-5c3.5-5.5 3.5-5.5 4-7c1.5 0 2.9 0 4.5 0c4.3-8.7 4.6-14 2.5-23.5c-.3-1-.7-2-1-3c.3 .1 .3 .1 2 .5c1.5 2.5 1.5 2.5 1.5 4c.5-.2 1-.3 1.5-.5c-.8-10-4.3-16-11.4-22.8c-3.7-3.8-5.5-6.9-7.6-11.7c-.7-1.5-1.5-2.9-2.3-4.4c-1.7-3.6-1.7-3.6-1.7-5.6c.3-.2 .7-.3 1-.5c-.5-3-.5-3-4.5-7c-2-1-3.9-2-6-3c-.7-.4-1.3-.6-2-1c0-.6 0-1.3 0-2zM33.5 83.5c-1 4-5.1 6.4-8 9c-8.6 8.1-14.6 17.2-15.2 29.2c.3 8.1 .3 8.1 2.2 11.8c.5 0 1 0 1.5 0c.4-2.9 .6-6 1-9c.6 0 1.3 0 2 0c.2 1.3 .4 2.7 .6 4.1c1.8 9.3 5.3 14.8 12.9 20.4c2.3 1.2 4.6 2.3 7 3.5c.7 .6 .7 .6 4.5 3.5c3.5 0 3.5 0 8.5-1.5c1 0 2 0 3 0c-1 2-1 2-2.5 3.5c-6.5 1.7-9.6 .9-16-1.5c-1.1-1-2.3-2-3.5-3c-1.5-.7-2.9-1.3-4.5-2c-3.9-3.4-6.9-6.5-9.5-11c-1.1-.5-2.3-1-3.5-1.5c-5.9-6.7-8-12.7-8-21.5c2.4-12.2 7.2-19.8 16.5-28c4.5-4 4.5-4 6-5.5c3.5-1 3.5-1 5-.5zM50.5 172c2.5 1 2.5 1 7 6.5c6 6.2 12 11.8 18.8 17.1c5.7 4.5 11.3 9.1 16.7 13.9c1.3 1.2 2.7 2.3 4 3.5c.7 .7 1.3 1.3 2 2c-.2 .8-.3 1.7-.5 2.5c-6 2.9-11.7 4-18.5 4c-2.6-2.6-4.3-5.5-5.5-9c-.3 .1-.3 .1-1.5 .5c0 2.3 0 4.6 0 7c-.3 .5-.7 1-1 1.5c-13.3 0-24.6-4.6-34.5-13.5c-4.5-4.5-4.5-4.5-8.5-11c.5-2.5 .5-2.5 4.5-7c5-3 5-3 8-3.5c.5 .3 1 .7 1.5 1c2-.5 2-.5 4-2c.9-4.2 1.7-8.3 2.5-12.5c.4-.3 .6-.7 1-1z"/><path fill="#03B5BD" d="M58.5 149.5c2.5 .5 2.5 .5 7 3.5c9.8 5.2 18.9 7.9 30 8.5c2.2 .5 4.3 1 6.5 1.5c7 0 7 0 9-.5c2.3-.2 4.6-.3 7-.5c3.2-.9 6.3-1.9 9.5-3c1.3-.2 2.7-.3 4-.5c.2 .2 .3 .3 .5 .5c-.8 6.3-4.1 10.2-8 15c-.2 .5-.3 1-.5 1.5c1.7-1 3.3-2 5-3c.7 .2 1.3 .3 2 .5c-3 12.2-9.3 20.3-17.5 29.5c-.7 .7-.7 .7-4 4.5c-1 .8-2 1.7-3 2.5c-3.5-.5-3.5-.5-8-5c-2.3-1.8-4.7-3.7-7-5.5c-2.7-2.2-5.3-4.3-8-6.5c-.8-.6-.8-.6-4.5-3.5c-11-9.3-20.7-18.8-28.5-31c3-4 3-4 8.5-8.5z"/><ellipse cx="94" cy="94" rx="6.5" ry="6.5" fill="#212E34"/><ellipse cx="135" cy="85.6" rx="6.2" ry="5.8" fill="#212E34"/><g fill="#FAF9F8"><circle cx="91.5" cy="91.5" r="2.1"/><circle cx="134.6" cy="84.3" r="2"/></g><ellipse cx="124" cy="93.8" rx="3.4" ry="1.1" fill="#9B9D9E" transform="rotate(-6 124 93.8)"/></g></g><rect x="5" y="5" width="230" height="230" rx="45" fill="none" stroke="#5B6B73" stroke-width="10"/></symbol></defs>
|
|
2
|
+
<rect x="1" y="1" width="958" height="438" rx="18" fill="#151D22" stroke="#34434B" stroke-width="1.5"/>
|
|
3
|
+
<path d="M389.1 186.4 403.2 182.6 417.1 178.5 430.8 174 444.2 169.3 457.5 164.3 470.6 159 483.6 153.6 496.5 148.1 509.2 142.4 521.9 136.7 534.6 131 547.1 125.3 559.7 119.6 572.3 114 584.9 108.5 597.5 103.2 610.2 98.1 623 93.3 635.9 88.7 648.9 84.4 662.1 80.5 675.4 76.9 688.9 73.8 702.6 71.2 716.6 69 730.8 67.4 745.2 66.4 760 66 A2 2 0 0 1 760 62 L760 62 745.1 62.2 730.4 63.1 716 64.5 701.9 66.5 687.9 69 674.2 72 660.7 75.4 647.3 79.2 634.1 83.3 621 87.8 608.1 92.5 595.2 97.5 582.4 102.7 569.7 108 557 113.5 544.4 119 531.7 124.6 519 130.1 506.3 135.7 493.5 141.1 480.6 146.5 467.7 151.7 454.6 156.7 441.4 161.5 428.1 166 414.6 170.2 400.9 174.1 386.9 177.6 A4.5 4.5 0 0 1 389.1 186.4Z" fill="#21BEC5"/>
|
|
4
|
+
<path d="M386.9 230.4 400.9 233.9 414.6 237.8 428.1 242 441.4 246.6 454.6 251.5 467.7 256.5 480.6 261.8 493.5 267.3 506.3 272.8 519 278.4 531.7 284.1 544.3 289.8 557 295.4 569.7 301 582.4 306.4 595.2 311.7 608.1 316.8 621 321.6 634.1 326.2 647.3 330.4 660.7 334.3 674.2 337.8 687.9 340.8 701.8 343.4 716 345.4 730.4 346.9 745.1 347.8 760 348 A2 2 0 0 1 760 344 L760 344 745.2 343.6 730.8 342.5 716.6 340.9 702.6 338.7 688.9 336 675.4 332.8 662.1 329.2 648.9 325.2 635.9 320.9 623 316.2 610.2 311.2 597.5 306 584.9 300.6 572.3 295 559.7 289.3 547.2 283.5 534.6 277.7 522 271.9 509.3 266.1 496.5 260.3 483.6 254.7 470.6 249.2 457.5 243.9 444.2 238.8 430.8 234 417.1 229.5 403.2 225.4 389.1 221.6 A4.5 4.5 0 0 1 386.9 230.4Z" fill="#21BEC5"/>
|
|
5
|
+
<path d="M300.4 200.5 294.9 200 289.3 199.4 283.6 199 277.7 198.6 271.7 198.2 265.6 197.9 259.4 197.6 253.2 197.3 246.8 197.1 240.4 197 233.9 196.9 227.4 196.8 220.8 196.8 214.2 196.9 207.6 197 201 197.1 194.4 197.3 187.7 197.5 181.1 197.8 174.5 198.1 168 198.4 161.5 198.8 155 199.3 148.6 199.8 142.3 200.3 136 200.9 129.9 201.6 123.8 202.3 A1.8 1.8 0 0 1 124.2 205.7 L124.2 205.7 130.2 205.2 136.4 204.7 142.6 204.2 148.9 203.8 155.3 203.4 161.7 203.1 168.2 202.8 174.7 202.6 181.3 202.4 187.9 202.2 194.5 202.1 201.1 202.1 207.7 202.1 214.2 202.1 220.8 202.2 227.3 202.3 233.8 202.5 240.2 202.7 246.6 203 252.9 203.3 259.1 203.7 265.2 204.1 271.3 204.5 277.2 205 283 205.6 288.7 206.2 294.2 206.8 299.6 207.5 A3.5 3.5 0 0 1 300.4 200.5Z" fill="#21BEC5"/>
|
|
6
|
+
<path d="M770 94C740 150 690 180 656 193" fill="none" stroke="#7E8A90" stroke-width="2.4" stroke-linecap="round" stroke-dasharray="0.1 7"/>
|
|
7
|
+
<path d="M770 316C740 260 690 232 656 220" fill="none" stroke="#7E8A90" stroke-width="2.4" stroke-linecap="round" stroke-dasharray="0.1 7"/>
|
|
8
|
+
<path d="M600 205C540 205 450 205 388 205" fill="none" stroke="#21BEC5" stroke-width="2.4" stroke-linecap="round"/>
|
|
9
|
+
<path d="M398 199l-8 6 8 6" fill="none" stroke="#21BEC5" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/>
|
|
10
|
+
<circle class="k1" opacity="0" r="4.6" fill="#21BEC5" stroke="#151D22" stroke-width="2"/>
|
|
11
|
+
<circle class="k2" opacity="0" r="4.6" fill="#21BEC5" stroke="#151D22" stroke-width="2"/>
|
|
12
|
+
<circle class="k3" opacity="0" r="3.6" fill="#7E8A90" stroke="#151D22" stroke-width="2"/>
|
|
13
|
+
<circle class="k4" opacity="0" r="3.6" fill="#7E8A90" stroke="#151D22" stroke-width="2"/>
|
|
14
|
+
<circle class="k5" opacity="0" r="3.8" fill="#21BEC5" stroke="#151D22" stroke-width="2"/>
|
|
15
|
+
<circle class="k6" opacity="0" r="4.6" fill="#F0B455" stroke="#151D22" stroke-width="2"/>
|
|
16
|
+
<circle class="k7" opacity="0" r="4.6" fill="#F0B455" stroke="#151D22" stroke-width="2"/>
|
|
17
|
+
<circle class="k8" opacity="0" r="4.6" fill="#21BEC5" stroke="#151D22" stroke-width="2"/>
|
|
18
|
+
<ellipse cx="344" cy="255" rx="46" ry="5" fill="#BCC5CA" opacity="0.12"/>
|
|
19
|
+
<use href="#jack" x="300" y="160" width="88" height="88"/>
|
|
20
|
+
<ellipse cx="790.0" cy="100" rx="34" ry="4" fill="#BCC5CA" opacity="0.12"/>
|
|
21
|
+
<rect x="760" y="34" width="60" height="60" rx="12" fill="#212E34" stroke="#5B6B73" stroke-width="2.4"/><circle cx="790" cy="64" r="11.4" fill="#21BEC5"/>
|
|
22
|
+
<g fill="none" stroke="#21BEC5" stroke-width="3" stroke-linecap="round"><path d="M824 30l7-9"/><path d="M830 38l9-3.4"/></g>
|
|
23
|
+
<ellipse cx="790.0" cy="382" rx="34" ry="4" fill="#BCC5CA" opacity="0.12"/>
|
|
24
|
+
<rect x="760" y="316" width="60" height="60" rx="12" fill="#212E34" stroke="#5B6B73" stroke-width="2.4"/><circle cx="790" cy="346" r="11.4" fill="#21BEC5"/>
|
|
25
|
+
<g fill="none" stroke="#21BEC5" stroke-width="3" stroke-linecap="round"><path d="M824 312l7-9"/><path d="M830 320l9-3.4"/></g>
|
|
26
|
+
<rect x="600" y="177" width="56" height="56" rx="11.2" fill="#212E34" stroke="#5B6B73" stroke-width="2.2"/>
|
|
27
|
+
<circle cx="622" cy="205" r="5.5" fill="#21BEC5"/>
|
|
28
|
+
<g fill="none" stroke="#21BEC5" stroke-width="3" stroke-linecap="round"><path d="M630 196a12 12 0 0 1 0 18"/><path d="M637 190a20 20 0 0 1 0 30"/></g>
|
|
29
|
+
<rect x="64" y="174" width="60" height="60" rx="12" fill="#212E34" stroke="#5B6B73" stroke-width="2.4"/>
|
|
30
|
+
<circle cx="94" cy="197" r="7.5" fill="#FAF9F8"/><path d="M81 220a13 12 0 0 1 26 0z" fill="#FAF9F8"/>
|
|
31
|
+
<text x="344" y="276" class="s" font-size="15" fill="#E6EDF3" font-weight="700" text-anchor="middle">Coordinator</text>
|
|
32
|
+
<text x="344" y="296" class="m" font-size="13" fill="#9B9D9E" text-anchor="middle">/jh-orchestrate</text>
|
|
33
|
+
<text x="344" y="314" class="s" font-size="12" fill="#9B9D9E" text-anchor="middle">main checkout</text>
|
|
34
|
+
<text x="790" y="124" class="s" font-size="15" fill="#E6EDF3" font-weight="700" text-anchor="middle">Lane a</text>
|
|
35
|
+
<text x="790" y="143" class="m" font-size="13" fill="#9B9D9E" text-anchor="middle">worktree a</text>
|
|
36
|
+
<text x="790" y="406" class="s" font-size="15" fill="#E6EDF3" font-weight="700" text-anchor="middle">Lane b</text>
|
|
37
|
+
<text x="790" y="425" class="m" font-size="13" fill="#9B9D9E" text-anchor="middle">worktree b</text>
|
|
38
|
+
<text x="628" y="259" class="m" font-size="14" fill="#E6EDF3" font-weight="700" text-anchor="middle">jh watch</text>
|
|
39
|
+
<text x="94" y="262" class="s" font-size="15" fill="#E6EDF3" font-weight="700" text-anchor="middle">You</text>
|
|
40
|
+
<text x="212" y="172" class="s" font-size="12.5" fill="#9B9D9E" text-anchor="middle">open decisions,</text>
|
|
41
|
+
<text x="212" y="189" class="s" font-size="12.5" fill="#9B9D9E" text-anchor="middle">one approval per PR</text>
|
|
42
|
+
<text x="212" y="232" class="s" font-size="12.5" fill="#9B9D9E" text-anchor="middle">asked as a choice</text>
|
|
43
|
+
<text x="520" y="104" class="s" font-size="12.5" fill="#9B9D9E" text-anchor="end">prompts →</text>
|
|
44
|
+
<text x="520" y="121" class="s" font-size="12.5" fill="#9B9D9E" text-anchor="end">← questions as text</text>
|
|
45
|
+
<text x="520" y="310" class="s" font-size="12.5" fill="#9B9D9E" text-anchor="end">prompts →</text>
|
|
46
|
+
<text x="520" y="327" class="s" font-size="12.5" fill="#9B9D9E" text-anchor="end">← questions as text</text>
|
|
47
|
+
<text x="494" y="196" class="s" font-size="12.5" fill="#9B9D9E" text-anchor="middle">wakes</text>
|
|
48
|
+
<text x="682" y="210" class="s" font-size="12.5" fill="#9B9D9E" text-anchor="start">state, PR,</text>
|
|
49
|
+
<text x="682" y="226" class="s" font-size="12.5" fill="#9B9D9E" text-anchor="start">checks</text>
|
|
50
|
+
</svg>
|