autonomous-sdlc-harness 0.1.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/LICENSE +201 -0
- package/NOTICE +7 -0
- package/README.md +24 -0
- package/dist/cli.js +194 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/config.js +561 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/daemon.js +791 -0
- package/dist/commands/daemon.js.map +1 -0
- package/dist/commands/doctor.js +336 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/init.js +2023 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/registry.js +42 -0
- package/dist/commands/registry.js.map +1 -0
- package/dist/config/check.js +505 -0
- package/dist/config/check.js.map +1 -0
- package/dist/config/io.js +177 -0
- package/dist/config/io.js.map +1 -0
- package/dist/config/model.js +406 -0
- package/dist/config/model.js.map +1 -0
- package/dist/core/errors.js +71 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/git.js +537 -0
- package/dist/core/git.js.map +1 -0
- package/dist/core/json.js +125 -0
- package/dist/core/json.js.map +1 -0
- package/dist/core/layerCoverage.js +141 -0
- package/dist/core/layerCoverage.js.map +1 -0
- package/dist/core/layerGapRemedy.js +62 -0
- package/dist/core/layerGapRemedy.js.map +1 -0
- package/dist/core/nameList.js +23 -0
- package/dist/core/nameList.js.map +1 -0
- package/dist/core/paths.js +153 -0
- package/dist/core/paths.js.map +1 -0
- package/dist/core/prompt.js +206 -0
- package/dist/core/prompt.js.map +1 -0
- package/dist/core/repoPaths.js +55 -0
- package/dist/core/repoPaths.js.map +1 -0
- package/dist/core/report.js +150 -0
- package/dist/core/report.js.map +1 -0
- package/dist/core/templating.js +88 -0
- package/dist/core/templating.js.map +1 -0
- package/dist/core/writer.js +479 -0
- package/dist/core/writer.js.map +1 -0
- package/dist/daemon/backend.js +180 -0
- package/dist/daemon/backend.js.map +1 -0
- package/dist/daemon/units.js +380 -0
- package/dist/daemon/units.js.map +1 -0
- package/dist/detect/nestedApplication.js +79 -0
- package/dist/detect/nestedApplication.js.map +1 -0
- package/dist/detect/presets.js +2033 -0
- package/dist/detect/presets.js.map +1 -0
- package/dist/detect/signals.js +1368 -0
- package/dist/detect/signals.js.map +1 -0
- package/dist/doctor/checks.js +3530 -0
- package/dist/doctor/checks.js.map +1 -0
- package/dist/generators/claudeContext.js +588 -0
- package/dist/generators/claudeContext.js.map +1 -0
- package/dist/generators/githooks.js +446 -0
- package/dist/generators/githooks.js.map +1 -0
- package/dist/generators/harnessConfig.js +632 -0
- package/dist/generators/harnessConfig.js.map +1 -0
- package/dist/generators/notifications.js +191 -0
- package/dist/generators/notifications.js.map +1 -0
- package/dist/generators/outerLoopScripts.js +165 -0
- package/dist/generators/outerLoopScripts.js.map +1 -0
- package/dist/generators/permissionProfile.js +1172 -0
- package/dist/generators/permissionProfile.js.map +1 -0
- package/dist/generators/projectSettings.js +322 -0
- package/dist/generators/projectSettings.js.map +1 -0
- package/dist/generators/repoRoot.js +417 -0
- package/dist/generators/repoRoot.js.map +1 -0
- package/dist/generators/scripts.js +557 -0
- package/dist/generators/scripts.js.map +1 -0
- package/dist/generators/stateDir.js +221 -0
- package/dist/generators/stateDir.js.map +1 -0
- package/dist/machine/paths.js +111 -0
- package/dist/machine/paths.js.map +1 -0
- package/dist/machine/plugins.js +224 -0
- package/dist/machine/plugins.js.map +1 -0
- package/dist/machine/registry.js +330 -0
- package/dist/machine/registry.js.map +1 -0
- package/package.json +23 -0
- package/scripts/README.md +13 -0
- package/scripts/daemon/launchd.plist.template +59 -0
- package/scripts/daemon/systemd.service.template +58 -0
- package/templates/README.md +15 -0
- package/templates/claude/CLAUDE.md +54 -0
- package/templates/claude/README.md +5 -0
- package/templates/claude/context/api.md +29 -0
- package/templates/claude/context/conventions.md +23 -0
- package/templates/claude/context/data-layer.md +28 -0
- package/templates/claude/context/data-storage.md +29 -0
- package/templates/claude/context/docs-catalog.md +29 -0
- package/templates/claude/context/domain.md +28 -0
- package/templates/claude/context/layer.md +20 -0
- package/templates/claude/context/module.md +30 -0
- package/templates/claude/context/package.md +29 -0
- package/templates/claude/context/presentation.md +32 -0
- package/templates/claude/context/state-slices.md +28 -0
- package/templates/claude/context/tests.md +28 -0
- package/templates/claude/harness-task-offer.md +58 -0
- package/templates/claude/push-notify.env.example +21 -0
- package/templates/claude/qa-accounts.env.example +38 -0
- package/templates/claude/qa_test_scenarios.md +110 -0
- package/templates/claude/settings.autonomous.json +93 -0
- package/templates/claude/settings.autonomous.qa.json +36 -0
- package/templates/githooks/README.md +3 -0
- package/templates/githooks/pre-push +72 -0
- package/templates/repo/README.md +3 -0
- package/templates/repo/gitattributes +16 -0
- package/templates/repo/gitignore +61 -0
- package/templates/repo/gitignore.qa +25 -0
- package/templates/repo/mcp.json +17 -0
- package/templates/scripts/README.md +5 -0
- package/templates/scripts/autonomous-format-stream.sh +95 -0
- package/templates/scripts/autonomous-notify.sh +337 -0
- package/templates/scripts/autonomous-watcher.sh +3087 -0
- package/templates/scripts/cleanup-merged-worktrees.sh +327 -0
- package/templates/scripts/commit-on-branch.sh +288 -0
- package/templates/scripts/create-worktree.sh +360 -0
- package/templates/scripts/deploy.sh +47 -0
- package/templates/scripts/lib/harness-run-lib.sh +1481 -0
- package/templates/scripts/push-branch.sh +140 -0
- package/templates/scripts/refresh-branch.sh +244 -0
- package/templates/scripts/restart-watcher.sh +401 -0
- package/templates/scripts/scratch-run.sh +302 -0
- package/templates/scripts/setup-worktree.sh +262 -0
- package/templates/scripts/start-dev-server.sh +99 -0
- package/templates/scripts/test.sh +50 -0
- package/templates/scripts/typecheck.sh +50 -0
- package/templates/state-dir/README-root.md +13 -0
- package/templates/state-dir/README.md +9 -0
- package/templates/state-dir/architecture_branch_review_point_reviews/README.md +9 -0
- package/templates/state-dir/architecture_branch_reviews/README.md +9 -0
- package/templates/state-dir/architecture_reviews/README.md +9 -0
- package/templates/state-dir/architecture_user_review_reviews/README.md +9 -0
- package/templates/state-dir/autonomous_inbox/README.md +9 -0
- package/templates/state-dir/autonomous_logs/README.md +9 -0
- package/templates/state-dir/branch_statistics/README.md +9 -0
- package/templates/state-dir/business_parity_branch_review_point_reviews/README.md +9 -0
- package/templates/state-dir/business_parity_branch_reviews/README.md +9 -0
- package/templates/state-dir/business_parity_reviews/README.md +9 -0
- package/templates/state-dir/business_parity_user_review_reviews/README.md +9 -0
- package/templates/state-dir/clarification_digests/README.md +9 -0
- package/templates/state-dir/clarifications/README.md +9 -0
- package/templates/state-dir/code_reviews/README.md +9 -0
- package/templates/state-dir/dispatch_additions/README.md +19 -0
- package/templates/state-dir/docs_catalog/README.md +9 -0
- package/templates/state-dir/flow_progress/README.md +9 -0
- package/templates/state-dir/improvement_observations/README.md +19 -0
- package/templates/state-dir/improvement_suggestions.md +29 -0
- package/templates/state-dir/lessons.md +23 -0
- package/templates/state-dir/qa_review_point_reviews/README.md +9 -0
- package/templates/state-dir/qa_reviews/README.md +9 -0
- package/templates/state-dir/review_plan_point_reviews/README.md +9 -0
- package/templates/state-dir/review_plan_reviews/README.md +9 -0
- package/templates/state-dir/scratch/README.md +11 -0
- package/templates/state-dir/skeptic_review_plan_reviews/README.md +9 -0
- package/templates/state-dir/skeptic_review_point_reviews/README.md +9 -0
- package/templates/state-dir/skeptic_reviews/README.md +9 -0
- package/templates/state-dir/story_plans/README.md +9 -0
- package/templates/state-dir/task_plan_point_reviews/README.md +9 -0
- package/templates/state-dir/task_plan_reviews/README.md +9 -0
- package/templates/state-dir/task_plans/README.md +9 -0
- package/templates/state-dir/task_prompts/README.md +9 -0
- package/templates/state-dir/ui_test_plan_reviews/README.md +9 -0
- package/templates/state-dir/ui_test_plans/README.md +9 -0
- package/templates/state-dir/user_review_fix_plan_point_reviews/README.md +9 -0
- package/templates/state-dir/user_reviews/README.md +9 -0
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generator: the caller-agnostic pre-push guard `init` writes into the configured `githooksDir`,
|
|
3
|
+
* and the one git setting that makes it run.
|
|
4
|
+
*
|
|
5
|
+
* **The rule this module exists to enforce: the branch names the guard protects come from
|
|
6
|
+
* configuration, and the guard is a backstop rather than a convention.** git runs a hook resolved
|
|
7
|
+
* through `core.hooksPath` on *every* push **git** performs, whoever invoked git — a person, a
|
|
8
|
+
* wrapper script, or a run whose own guard only ever sees the command string an agent submitted and
|
|
9
|
+
* therefore never sees a push spawned indirectly. Caller-agnostic *within git* is the whole of that
|
|
10
|
+
* claim, and the boundary is where git ends: a tool that implements push against the git backend
|
|
11
|
+
* itself, as `jj git push` does, performs no git push and runs no git hook, so this file is not on
|
|
12
|
+
* that path at all. Two things are — the session-level `PreToolUse` protected-branch guard the
|
|
13
|
+
* plugin installs, which sees `jj git push` and draws the identical decision on one whose **target
|
|
14
|
+
* is named** (measured), and a forge-side branch ruleset, which is the only true floor, which
|
|
15
|
+
* nothing here provisions, and which is the only cover for a `jj git push` that names no bookmark.
|
|
16
|
+
* `doctor`'s `jj-repository` check (`doctor/checks.ts`) is where an adopter on such a repository is
|
|
17
|
+
* told all three, and where the bound on the second one is stated in full. That is what makes this the last line of defence for the "never land on a
|
|
18
|
+
* protected branch" boundary within git, and why the protected set is substituted from the adopter's
|
|
19
|
+
* configuration rather than baked into the shipped script.
|
|
20
|
+
*
|
|
21
|
+
* **The set, and the one way it goes stale.** {@link resolveProtectedBranches} resolves
|
|
22
|
+
* *(`protectedBranches`, else that key's schema default)* ∪ *{`defaultBranch`}* — the same set
|
|
23
|
+
* `hr_protected_patterns` in `scripts/lib/harness-run-lib.sh` gives the git wrappers, so one
|
|
24
|
+
* configuration yields one set on both sides. What differs is **when**: the wrappers re-resolve it
|
|
25
|
+
* from `harness.config.json` on every call, while this hook carries the set that was substituted
|
|
26
|
+
* into its `case` label when `init` wrote it, because the hook is `create-if-absent`. So an edit to
|
|
27
|
+
* `protectedBranches` binds a wrapper at once and binds the hook only at `init --force` (or after
|
|
28
|
+
* deleting the hook and re-running `init`), and until then the two enforce different sets. A third
|
|
29
|
+
* run re-renders it as a **consequence** rather than as a way to change the set: `init
|
|
30
|
+
* --reset-config` rebuilds `harness.config.json` itself, and {@link writeGitHooks} re-renders the
|
|
31
|
+
* hook from the rebuilt file — after a `.bak` — but only when the `case` label the hook carried no
|
|
32
|
+
* longer matches the set that file resolves, and never when that label cannot be read. The
|
|
33
|
+
* asymmetry is the point: `--force` re-renders unconditionally, `--reset-config` re-renders only a
|
|
34
|
+
* hook its own rebuild made wrong. Which
|
|
35
|
+
* resolution is authoritative follows the caller: a **push** is judged by the `case` label in the
|
|
36
|
+
* written file, because git runs that file; a **wrapper** is judged by what the library returns.
|
|
37
|
+
* Both sides of that comparison are exported rather than re-derived by whoever needs them —
|
|
38
|
+
* {@link resolveProtectedBranches} for the configured set, {@link readProtectedCaseLabel} for the
|
|
39
|
+
* set a written hook carries, {@link caseLabelMatches} for whether the two agree, and
|
|
40
|
+
* {@link isGlobPattern} for whether an entry names one branch or a namespace — and the consumers
|
|
41
|
+
* they exist for are `doctor`'s `pre-push-guard` and `protected-set` checks and
|
|
42
|
+
* {@link writeGitHooks}' own re-render arm.
|
|
43
|
+
* This is the one artifact deliberately exempt from choice 1 in `generators/outerLoopScripts.ts` —
|
|
44
|
+
* git resolves a hook as a file at `core.hooksPath`, and `create-if-absent` keeps that file the
|
|
45
|
+
* adopter's to edit.
|
|
46
|
+
*
|
|
47
|
+
* ## Three non-obvious choices, and where each comes from
|
|
48
|
+
*
|
|
49
|
+
* 1. **`init` points `core.hooksPath` at the configured directory only when it is unset or already
|
|
50
|
+
* equal — never when it points somewhere else.** An adopter who has already wired their hooks
|
|
51
|
+
* somewhere has made a decision the harness does not get to overrule silently, and a hooks path
|
|
52
|
+
* is a single value: taking it over would disable every hook they had. The mismatch is reported
|
|
53
|
+
* with **both** values so the fix is a one-line choice rather than a search.
|
|
54
|
+
* 2. **The value written is repo-relative.** git resolves a relative `core.hooksPath` from the top
|
|
55
|
+
* of the working tree the hook runs in, so one value serves the repository and every additional
|
|
56
|
+
* working copy of it, each resolving to its own committed copy of the directory. An absolute
|
|
57
|
+
* path would pin every working copy to the first one that ran `init` — and the config file's
|
|
58
|
+
* paths are repo-relative anyway (`docs/config.md` §5).
|
|
59
|
+
* 3. **The protected set is validated before it is substituted.** The patterns land inside a shell
|
|
60
|
+
* `case`, where `|` separates alternatives and `)` ends the list, so an entry carrying either
|
|
61
|
+
* would silently change what the guard matches — or produce a script that does not parse, which
|
|
62
|
+
* on a hook means every push fails. A glob (`release/*`) is the one metacharacter that is meant
|
|
63
|
+
* to be there and is what `protectedBranches` documents as its own feature.
|
|
64
|
+
*
|
|
65
|
+
* ## Where the filesystem and git are touched, and why they are split
|
|
66
|
+
*
|
|
67
|
+
* {@link writeGitHooks} is a planner like every other generator: it enqueues the hook into the
|
|
68
|
+
* command's write plan and mutates nothing. It does **read** one file — the hook already at
|
|
69
|
+
* `<githooksDir>/<PRE_PUSH_HOOK>`, and only on the run that rebuilt the config — because the
|
|
70
|
+
* re-render arm's gate is what that file's `case` label says; a read is not a write, and the
|
|
71
|
+
* decision it feeds still reaches the filesystem only through the plan.
|
|
72
|
+
* {@link pointHooksPath} is the **mutation** — the CLI's
|
|
73
|
+
* only write to git configuration — and is a separate exported function so `init` calls it *after*
|
|
74
|
+
* the plan has been applied, when the hook it enables actually exists, and skips the write under
|
|
75
|
+
* `--dry-run` while still reporting what it would have done.
|
|
76
|
+
*
|
|
77
|
+
* That is also why the two `git` calls below are here rather than in `core/git.ts`: that module
|
|
78
|
+
* owns the *repository-state and path-resolution* probes, every one of which is read-only by
|
|
79
|
+
* design, while these read and write a repository **setting**. `core/git.ts` names this module as
|
|
80
|
+
* its one deliberate exception, and the two clauses are halves of the same rule: a third git
|
|
81
|
+
* invocation anywhere in the package belongs there rather than here. Both nonetheless keep its first invariant — `execFileSync` with an argv
|
|
82
|
+
* array and never a shell string. Its second — *literal* arguments with nothing interpolated —
|
|
83
|
+
* holds for the read, whose probe is a frozen constant, but not for the write: that one passes the
|
|
84
|
+
* configured `githooksDir` as an element of the argument vector. It is safe there precisely because
|
|
85
|
+
* there is no shell to re-split it, and because {@link resolveGithooksDir} has already refused the
|
|
86
|
+
* one value — the repository root — whose consequence would not be a one-key fix. That value is the
|
|
87
|
+
* module's only interpolated git argument, and a second one would need the same two guarantees
|
|
88
|
+
* spelled out before it is added.
|
|
89
|
+
*/
|
|
90
|
+
import { execFileSync } from 'node:child_process';
|
|
91
|
+
import { readFileSync } from 'node:fs';
|
|
92
|
+
import { join, resolve } from 'node:path';
|
|
93
|
+
import { CONFIG_FILENAME, DEFAULTS } from '../config/model.js';
|
|
94
|
+
import { HarnessError } from '../core/errors.js';
|
|
95
|
+
import { readTemplate } from '../core/paths.js';
|
|
96
|
+
import { normalizeRepoDir } from '../core/repoPaths.js';
|
|
97
|
+
import { renderTemplate } from '../core/templating.js';
|
|
98
|
+
/** The templates' subdirectory under `cli/templates/`, addressed as {@link readTemplate} wants it. */
|
|
99
|
+
const TEMPLATE_DIR = 'githooks';
|
|
100
|
+
/** The hook's file name, in the template tree and in the adopter's `githooksDir` alike. */
|
|
101
|
+
export const PRE_PUSH_HOOK = 'pre-push';
|
|
102
|
+
/** Executable, because git runs the file rather than sourcing it. */
|
|
103
|
+
const HOOK_MODE = 0o755;
|
|
104
|
+
/** The git setting that decides which directory git looks for hooks in. */
|
|
105
|
+
const HOOKS_PATH_KEY = 'core.hooksPath';
|
|
106
|
+
/** The read probe, as one frozen literal so a review can see no flag was added to it. */
|
|
107
|
+
const GIT_READ_HOOKS_PATH = Object.freeze(['config', '--get', HOOKS_PATH_KEY]);
|
|
108
|
+
/** git's exit status for `config --get <key>` when the key is simply not set anywhere. */
|
|
109
|
+
const GIT_KEY_NOT_FOUND = 1;
|
|
110
|
+
/** The token the protected-branch `case` pattern is substituted into. */
|
|
111
|
+
const CASE_TOKEN = 'protectedBranchesCase';
|
|
112
|
+
/** How the `case` list separates its alternatives. */
|
|
113
|
+
const CASE_SEPARATOR = '|';
|
|
114
|
+
/** The `case` statement the template opens; the label {@link CASE_TOKEN} renders is the line below it. */
|
|
115
|
+
const CASE_OPENER = 'case "$target" in';
|
|
116
|
+
/** How a `case` label ends, which is what makes the label tellable from the lines around it. */
|
|
117
|
+
const CASE_LABEL_END = ')';
|
|
118
|
+
/**
|
|
119
|
+
* What a protected-branch pattern may not contain, and how a message names it.
|
|
120
|
+
*
|
|
121
|
+
* Everything that ends a `case` label, separates its alternatives, or opens a substitution — a
|
|
122
|
+
* pattern carrying one of these changes what the guard matches, or produces a hook that does not
|
|
123
|
+
* parse, in which case every push fails. `*`, `?` and `[…]` are deliberately absent from the list:
|
|
124
|
+
* a `case` label is a glob, and a glob entry is what `protectedBranches` documents as the way to
|
|
125
|
+
* protect a whole namespace with one entry.
|
|
126
|
+
*/
|
|
127
|
+
const FORBIDDEN_IN_PATTERN = Object.freeze([
|
|
128
|
+
[CASE_SEPARATOR, 'a case-alternation separator (|)'],
|
|
129
|
+
[')', 'a parenthesis, which ends a case label'],
|
|
130
|
+
['(', 'a parenthesis'],
|
|
131
|
+
[';', 'a shell operator (;)'],
|
|
132
|
+
['&', 'a shell operator (&)'],
|
|
133
|
+
['$', 'a shell substitution ($)'],
|
|
134
|
+
['`', 'a backquote'],
|
|
135
|
+
['"', 'a double quote'],
|
|
136
|
+
["'", 'a single quote'],
|
|
137
|
+
['\\', 'a backslash'],
|
|
138
|
+
['<', 'a shell redirect (<)'],
|
|
139
|
+
['>', 'a shell redirect (>)'],
|
|
140
|
+
[' ', 'a space'],
|
|
141
|
+
['\t', 'a tab'],
|
|
142
|
+
['\n', 'a newline'],
|
|
143
|
+
]);
|
|
144
|
+
/**
|
|
145
|
+
* The three characters {@link FORBIDDEN_IN_PATTERN} deliberately admits, read from the other side:
|
|
146
|
+
* what makes an entry a pattern rather than a branch name.
|
|
147
|
+
*/
|
|
148
|
+
const GLOB_METACHARACTERS = Object.freeze(['*', '?', '[']);
|
|
149
|
+
/**
|
|
150
|
+
* Whether a protected-branch entry is a glob — an entry the `case` label matches by pattern rather
|
|
151
|
+
* than by name.
|
|
152
|
+
*
|
|
153
|
+
* **Exported so a consumer that has to tell a namespace entry from a branch name reads the character
|
|
154
|
+
* set here** rather than spelling a second one that could drift from what
|
|
155
|
+
* {@link FORBIDDEN_IN_PATTERN} admits. The consumer is `doctor`'s `protected-set` check, which looks
|
|
156
|
+
* every listed entry up as a branch and cannot look a glob up at all: `release/*` names no single
|
|
157
|
+
* ref, and it is what `protectedBranches` documents as the way to protect a whole namespace with one
|
|
158
|
+
* entry — including a namespace an adopter wrote ahead of using it.
|
|
159
|
+
*/
|
|
160
|
+
export function isGlobPattern(entry) {
|
|
161
|
+
return GLOB_METACHARACTERS.some((needle) => entry.includes(needle));
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* The set a **written** hook enforces — the alternatives of its `case` label — or `undefined` when
|
|
165
|
+
* the file carries no label to read. {@link writeGitHooks}' inverse, and the only way to learn what
|
|
166
|
+
* a hook on disk protects.
|
|
167
|
+
*
|
|
168
|
+
* It reads that **label**, not the file as a whole, which is the discipline `docs/development.md`
|
|
169
|
+
* §5 gate 7 leg (iv) states as *"Read that label specifically, and not the file as a whole"*: the
|
|
170
|
+
* hook's own header names `release/*` as an example entry, so a search for a branch name across the
|
|
171
|
+
* file answers yes on a hook whose guard would never match it.
|
|
172
|
+
*
|
|
173
|
+
* Nothing is unescaped, because nothing was escaped: {@link FORBIDDEN_IN_PATTERN} refused every
|
|
174
|
+
* character that would change how the label parses before it was written, which is what makes a
|
|
175
|
+
* split on {@link CASE_SEPARATOR} exact rather than a best effort.
|
|
176
|
+
*
|
|
177
|
+
* **`undefined` is the answer for every shape that is not a readable label, and an empty list is
|
|
178
|
+
* never returned.** A hook an adopter rewrote is a legitimate state a consumer has to be able to
|
|
179
|
+
* report *as* unreadable; answering it with the empty set would report it as protecting nothing,
|
|
180
|
+
* which is the one state {@link resolveProtectedBranches} exists to keep a guard out of.
|
|
181
|
+
*/
|
|
182
|
+
export function readProtectedCaseLabel(hookText) {
|
|
183
|
+
const lines = hookText.split('\n');
|
|
184
|
+
const opener = lines.findIndex((line) => line.includes(CASE_OPENER));
|
|
185
|
+
if (opener === -1)
|
|
186
|
+
return undefined;
|
|
187
|
+
const label = lines.slice(opener + 1).find((line) => line.trim() !== '')?.trim();
|
|
188
|
+
if (label === undefined || !label.endsWith(CASE_LABEL_END))
|
|
189
|
+
return undefined;
|
|
190
|
+
const patterns = label
|
|
191
|
+
.slice(0, -CASE_LABEL_END.length)
|
|
192
|
+
.trim()
|
|
193
|
+
.split(CASE_SEPARATOR)
|
|
194
|
+
.map((pattern) => pattern.trim());
|
|
195
|
+
return patterns.some((pattern) => pattern === '') ? undefined : patterns;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Whether a label read out of a hook and a resolved set are the same set — the one comparison every
|
|
199
|
+
* consumer of the two readers above makes, so that none of them writes its own.
|
|
200
|
+
*
|
|
201
|
+
* **Order- and duplicate-insensitive.** A `case` list is an alternation, in which order carries no
|
|
202
|
+
* meaning, and {@link resolveProtectedBranches} de-duplicates through a `Set` before a label is
|
|
203
|
+
* rendered from it. Reporting a hand-reordered but equivalent label as drift would be a check
|
|
204
|
+
* nobody keeps green, and the adopter who edited that label by hand is exactly the reader it has to
|
|
205
|
+
* stay useful to.
|
|
206
|
+
*/
|
|
207
|
+
export function caseLabelMatches(label, resolved) {
|
|
208
|
+
const written = new Set(label);
|
|
209
|
+
const configured = new Set(resolved);
|
|
210
|
+
return written.size === configured.size && [...written].every((pattern) => configured.has(pattern));
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* The configured hooks directory, refused when it names the repository root.
|
|
214
|
+
*
|
|
215
|
+
* `core.hooksPath` makes git treat *every* file in the directory it names as a candidate hook, so
|
|
216
|
+
* pointing it at the root would put the repository's whole top level in that role. The fix is one
|
|
217
|
+
* key, so it carries the ordinary exit code.
|
|
218
|
+
*
|
|
219
|
+
* **Exported so a check grades the hook at the path the generator wrote it to**, rather than
|
|
220
|
+
* resolving a second one. It still throws on that value from a `doctor` check as it does from
|
|
221
|
+
* `init`, and that is the intended handling: `runChecks` turns a thrown value into that check's own
|
|
222
|
+
* `fail` naming the check that threw, so a caller catching it would be replacing a reported failure
|
|
223
|
+
* with a quieter one (`doctor/checks.ts` → `runChecks`).
|
|
224
|
+
*/
|
|
225
|
+
export function resolveGithooksDir(config) {
|
|
226
|
+
const normalized = normalizeRepoDir((config.githooksDir ?? DEFAULTS.githooksDir).trim());
|
|
227
|
+
if (normalized === '.' || normalized === '') {
|
|
228
|
+
throw new HarnessError(`githooksDir is ${JSON.stringify(config.githooksDir)}, which names the repository root rather than a directory in it: git treats every file in the directory core.hooksPath names as a candidate hook, so the repository's whole top level would be in that role. Set githooksDir in harness.config.json to a directory name`);
|
|
229
|
+
}
|
|
230
|
+
return normalized;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* The patterns the guard refuses a push to: `protectedBranches` — or, when the key is absent, that
|
|
234
|
+
* key's schema default — trimmed and de-duplicated, then **unioned with `defaultBranch`**.
|
|
235
|
+
*
|
|
236
|
+
* The union is what makes the hook enforce the set `hr_protected_patterns` resolves for the git
|
|
237
|
+
* wrappers, which unions `defaultBranch` too. Without it the two disagreed in the direction that
|
|
238
|
+
* fails silently: a `protectedBranches` not listing the default branch left the wrappers refusing a
|
|
239
|
+
* commit on it while the hook — the last line of defence, and the only one a push spawned
|
|
240
|
+
* indirectly reaches — let the push through. A union only ever widens a refusal, so the branch work
|
|
241
|
+
* merges back into is protected whatever the list says, and `defaultBranch` faces the same
|
|
242
|
+
* {@link FORBIDDEN_IN_PATTERN} check as a listed entry because it lands in the same `case` label.
|
|
243
|
+
*
|
|
244
|
+
* An empty list is therefore exactly "only the default branch", and says so. Protecting nothing is
|
|
245
|
+
* the one reading of an empty list that leaves the guard as a hook that runs on every push and
|
|
246
|
+
* never refuses one — a backstop that silently protects nothing is worse than no backstop, because
|
|
247
|
+
* its presence is what everything else relies on.
|
|
248
|
+
*
|
|
249
|
+
* **Exported so a consumer comparing a written hook against the configuration resolves the set
|
|
250
|
+
* here** rather than spelling the union out a second time. `warn` stays a required parameter: the
|
|
251
|
+
* empty-list warning belongs to the run that *writes* a hook, so a consumer that only wants the set
|
|
252
|
+
* passes a no-op and reports the state it found in its own words.
|
|
253
|
+
*/
|
|
254
|
+
export function resolveProtectedBranches(config, warn) {
|
|
255
|
+
const configured = (config.protectedBranches ?? DEFAULTS.protectedBranches).map((entry) => entry.trim());
|
|
256
|
+
const listed = [...new Set(configured.filter((entry) => entry !== ''))];
|
|
257
|
+
const defaultBranch = config.defaultBranch.trim() === '' ? DEFAULTS.defaultBranch : config.defaultBranch.trim();
|
|
258
|
+
if (listed.length === 0) {
|
|
259
|
+
warn(`protectedBranches lists no branch, so the pre-push guard was written to protect ${JSON.stringify(defaultBranch)} and nothing else - the branch work merges back into, which is in the set whatever the list says. A guard that refuses nothing runs on every push and stops none of them, which is the one state it must not be in: list the branches in protectedBranches in ${CONFIG_FILENAME} (or set the key with \`config set protectedBranches '<json>'\`), then re-run init --force, which copies the generated ${PRE_PUSH_HOOK} hook to a .bak sibling and re-renders it from the config in effect - --force overwrites generated files but never ${CONFIG_FILENAME}, which init reads on every run, so the list just written is the one the hook is rendered from. Without --force: delete the generated ${PRE_PUSH_HOOK} hook and re-run init, which re-renders it because the hook is create-if-absent`);
|
|
260
|
+
}
|
|
261
|
+
const patterns = [...new Set([...listed, defaultBranch])];
|
|
262
|
+
for (const pattern of patterns) {
|
|
263
|
+
const source = listed.includes(pattern) ? 'protectedBranches entry' : 'defaultBranch';
|
|
264
|
+
for (const [needle, described] of FORBIDDEN_IN_PATTERN) {
|
|
265
|
+
if (!pattern.includes(needle))
|
|
266
|
+
continue;
|
|
267
|
+
throw new HarnessError(`the ${source} ${JSON.stringify(pattern)} contains ${described}, and the protected set is substituted into a shell case list in the pre-push hook: an entry carrying one either changes which branches the guard matches or leaves a hook that does not parse, in which case every push fails. Use a glob such as release/* to cover a namespace, and keep each entry a single branch pattern`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return patterns;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Render the hook through {@link renderTemplate}.
|
|
274
|
+
*
|
|
275
|
+
* **`assertNoneSurvive` is off**: the value substituted in is the protected-branch set, which is the
|
|
276
|
+
* adopter's, and {@link FORBIDDEN_IN_PATTERN} has already refused every character that could change
|
|
277
|
+
* what the guard matches. A branch pattern that merely *looked* like a token is not a fault in this
|
|
278
|
+
* CLI, which is all the post-check would be able to report it as.
|
|
279
|
+
*/
|
|
280
|
+
function renderHook(values) {
|
|
281
|
+
return renderTemplate(readTemplate(`${TEMPLATE_DIR}/${PRE_PUSH_HOOK}`), values, {
|
|
282
|
+
describe: `the ${PRE_PUSH_HOOK} template`,
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
/** The generator's one read: the hook that is there, graded against the set just resolved. */
|
|
286
|
+
function inspectWrittenGuard(hookFile, resolved) {
|
|
287
|
+
let hookText;
|
|
288
|
+
try {
|
|
289
|
+
hookText = readFileSync(hookFile, 'utf8');
|
|
290
|
+
}
|
|
291
|
+
catch {
|
|
292
|
+
return { kind: 'absent' };
|
|
293
|
+
}
|
|
294
|
+
const label = readProtectedCaseLabel(hookText);
|
|
295
|
+
if (label === undefined)
|
|
296
|
+
return { kind: 'unreadable' };
|
|
297
|
+
return caseLabelMatches(label, resolved) ? { kind: 'equal' } : { kind: 'stale', label };
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Enqueue the pre-push guard: `ensure-dir` on the configured `githooksDir`, then the rendered hook
|
|
301
|
+
* as `create-if-absent` with the executable bit.
|
|
302
|
+
*
|
|
303
|
+
* `create-if-absent` is the re-run contract for this artifact: the adopter may have tightened the
|
|
304
|
+
* guard, and a second `init` leaves an edited hook byte-identical — which is why the note below says
|
|
305
|
+
* what a **kept** hook carries rather than restating the set the config in effect holds. `--force`
|
|
306
|
+
* regenerates the hook, after the write engine has copied it to a `.bak` sibling, and that *is* the
|
|
307
|
+
* way to pick up a change to `protectedBranches`: config is read-if-present on every run, so an edit
|
|
308
|
+
* living only in `harness.config.json` survives `--force` and is what the hook is re-rendered from.
|
|
309
|
+
* Without `--force`, deleting the hook and re-running `init` re-renders it the same way, and is the
|
|
310
|
+
* way to pick the change up without regenerating any other artifact.
|
|
311
|
+
*
|
|
312
|
+
* **The one run that also re-renders it is the run that rebuilt the config it is rendered from.**
|
|
313
|
+
* The artifact stays `create-if-absent`; what changes is that a `--reset-config` run
|
|
314
|
+
* ({@link GitHooksOptions.configRebuilt}) answers the overwrite question for *this* request with
|
|
315
|
+
* `forceOverride: 'always'` when — and only when — {@link inspectWrittenGuard} reports the written
|
|
316
|
+
* label `stale` against the set the rebuilt config resolves. Without it the documented remedy
|
|
317
|
+
* produced the defect: `--reset-config --default-branch <name>` is what corrects `defaultBranch`,
|
|
318
|
+
* and it left the guard enforcing the branch set it was written with. The asymmetry with `--force`
|
|
319
|
+
* is deliberate — `--force` re-renders unconditionally, this run re-renders only a hook its own
|
|
320
|
+
* rebuild made wrong — and the other three states are left byte-identical, an `unreadable` one with
|
|
321
|
+
* a warning naming `doctor`'s `pre-push-guard` check.
|
|
322
|
+
*
|
|
323
|
+
* Nothing here writes to the filesystem or touches git: the generator reads the hook that is there,
|
|
324
|
+
* plans, `init` applies the plan, and {@link pointHooksPath} is called afterwards to make the hook
|
|
325
|
+
* take effect.
|
|
326
|
+
*/
|
|
327
|
+
export function writeGitHooks({ repoRoot, config, plan, configRebuilt = false, dryRun = false, }) {
|
|
328
|
+
const warnings = [];
|
|
329
|
+
const notes = [];
|
|
330
|
+
const githooksDir = resolveGithooksDir(config);
|
|
331
|
+
const protectedBranches = resolveProtectedBranches(config, (message) => warnings.push(message));
|
|
332
|
+
const hookPath = `${githooksDir}/${PRE_PUSH_HOOK}`;
|
|
333
|
+
// Read only where the run may act on the answer, so an ordinary re-run does not so much as open
|
|
334
|
+
// the file it is contractually required to keep.
|
|
335
|
+
const written = configRebuilt
|
|
336
|
+
? inspectWrittenGuard(join(repoRoot, hookPath), protectedBranches)
|
|
337
|
+
: undefined;
|
|
338
|
+
const reRender = written?.kind === 'stale';
|
|
339
|
+
plan.add({ path: join(repoRoot, githooksDir), policy: 'ensure-dir', label: 'git hooks directory' });
|
|
340
|
+
plan.add({
|
|
341
|
+
path: join(repoRoot, hookPath),
|
|
342
|
+
policy: 'create-if-absent',
|
|
343
|
+
content: renderHook({ [CASE_TOKEN]: protectedBranches.join(CASE_SEPARATOR) }),
|
|
344
|
+
label: `git hook ${PRE_PUSH_HOOK}`,
|
|
345
|
+
mode: HOOK_MODE,
|
|
346
|
+
// The write engine's own mechanism, not a fifth policy: `'always'` copies the target to
|
|
347
|
+
// `<path>.bak` and replaces it on a run with no --force at all (`core/writer.ts`).
|
|
348
|
+
...(reRender ? { forceOverride: 'always' } : {}),
|
|
349
|
+
});
|
|
350
|
+
// "A hook that was already there is kept" is a claim about *this* run, so it may not stand on the
|
|
351
|
+
// run that re-rendered the file, one note below the line saying so. The re-render arm states the
|
|
352
|
+
// artifact's property and leaves what happened to that note, which is the one that carries a tense.
|
|
353
|
+
const keptClause = reRender
|
|
354
|
+
? `so the hook carries the set that was substituted into its case label`
|
|
355
|
+
: `so a hook that was already there is kept exactly as it is and still carries the set it was written with`;
|
|
356
|
+
notes.push(`${hookPath} is generated to refuse a push whose target branch is ${protectedBranches.join(', ')}, whoever invokes git - on every push git itself performs, which is not every push: a tool that implements push against the git backend directly, as \`jj git push\` does, runs no git hook, and doctor's jj-repository check reports that where it applies. The set is protectedBranches unioned with defaultBranch, which is in the set whatever the list says. It is committed, so it travels with the checkout, and the branch set is substituted at write time - ${keptClause}, while the git wrappers re-read ${CONFIG_FILENAME} on every call: until the hook is re-rendered the two can enforce different sets, and a push is judged by this file. To change the set: edit the case list in ${hookPath} by hand and keep protectedBranches in ${CONFIG_FILENAME} in step with it, or edit that key (\`config set protectedBranches '<json>'\`) and re-run init --force, which copies ${hookPath} to ${hookPath}.bak and re-renders it from the config in effect - --force overwrites generated files but never ${CONFIG_FILENAME}, which init reads on every run, so the edited set is the one the hook is rendered from. Without --force: delete ${hookPath} and re-run init, which re-renders the hook because it is create-if-absent`);
|
|
357
|
+
if (written?.kind === 'stale') {
|
|
358
|
+
notes.push(dryRun
|
|
359
|
+
? `${hookPath} carries the case label ${written.label.join(', ')} and the ${CONFIG_FILENAME} this run would rebuild resolves ${protectedBranches.join(', ')}, so the hook would be re-rendered from the rebuilt file and the file that is there would be copied to ${hookPath}.bak first - a guard left carrying the old label is what enforces a branch set the rebuild made wrong. Any hand edit in it would be in that .bak rather than in the guard git then runs, so anything you meant to keep would have to be put back by hand. This was a dry run, so nothing was written and nothing was backed up`
|
|
360
|
+
: `${hookPath} carried the case label ${written.label.join(', ')} and the rebuilt ${CONFIG_FILENAME} resolves ${protectedBranches.join(', ')}, so the hook was re-rendered from it and the file that was there is at ${hookPath}.bak - a guard left carrying the old label is what enforces a branch set the rebuild made wrong. Any hand edit in it is in that .bak rather than in the guard git now runs, so anything you meant to keep has to be put back by hand`);
|
|
361
|
+
}
|
|
362
|
+
// Tense-free on purpose, unlike the note above it: this arm writes nothing in either mode, so
|
|
363
|
+
// there is no completion to claim and a --dry-run wording would be the same sentence.
|
|
364
|
+
if (written?.kind === 'unreadable') {
|
|
365
|
+
warnings.push(`${hookPath} is there but carries no case label in the generated shape, so what it protects cannot be read out of it and cannot be compared with the protected set this run resolved (${protectedBranches.join(', ')}): a hook in that state is left exactly as it is, because rebuilding ${CONFIG_FILENAME} is not consent to discard a guard somebody wrote by hand. \`doctor\`'s pre-push-guard check reports this state on every run: read the file to see what it refuses, then either put the set back in its case label by hand or delete ${hookPath} and re-run init, which re-renders it because the hook is create-if-absent`);
|
|
366
|
+
}
|
|
367
|
+
return { githooksDir, hookPath, protectedBranches, warnings, notes };
|
|
368
|
+
}
|
|
369
|
+
/** One git invocation, with the arguments passed as an argv array and stdin closed. */
|
|
370
|
+
function runGit(args, cwd) {
|
|
371
|
+
return execFileSync('git', [...args], {
|
|
372
|
+
cwd,
|
|
373
|
+
encoding: 'utf8',
|
|
374
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
375
|
+
windowsHide: true,
|
|
376
|
+
}).trim();
|
|
377
|
+
}
|
|
378
|
+
/** What the setting holds now: its value, `undefined` when unset, or why it could not be read. */
|
|
379
|
+
function readHooksPath(repoRoot) {
|
|
380
|
+
try {
|
|
381
|
+
const value = runGit(GIT_READ_HOOKS_PATH, repoRoot);
|
|
382
|
+
return value === '' ? {} : { value };
|
|
383
|
+
}
|
|
384
|
+
catch (error) {
|
|
385
|
+
const failure = error;
|
|
386
|
+
if (failure.status === GIT_KEY_NOT_FOUND)
|
|
387
|
+
return {};
|
|
388
|
+
if (failure.code === 'ENOENT')
|
|
389
|
+
return { error: 'git is not on PATH' };
|
|
390
|
+
return { error: failure.message };
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
/** True when two spellings of a hooks path name the same directory. Absolute and relative both. */
|
|
394
|
+
function sameDirectory(repoRoot, existing, configured) {
|
|
395
|
+
return resolve(repoRoot, existing) === resolve(repoRoot, configured);
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Point `core.hooksPath` at the configured `githooksDir` — **only** when it is unset or already
|
|
399
|
+
* resolves there.
|
|
400
|
+
*
|
|
401
|
+
* This is the CLI's one write to git configuration, and the whole reason it is a separate function
|
|
402
|
+
* from the generator above: `init` calls it after the write plan has been applied, so the setting
|
|
403
|
+
* is never made to name a directory whose hook has not been written yet, and a `--dry-run` reports
|
|
404
|
+
* the decision without making it.
|
|
405
|
+
*
|
|
406
|
+
* Every outcome that is not `set` leaves the repository exactly as it was and explains itself: a
|
|
407
|
+
* hooks path pointing elsewhere is the adopter's own wiring, and taking it over would disable every
|
|
408
|
+
* hook they have there. None of them fails the command — the rest of `init` is unaffected, and the
|
|
409
|
+
* fix is a single documented command.
|
|
410
|
+
*/
|
|
411
|
+
export function pointHooksPath({ repoRoot, githooksDir, dryRun = false }) {
|
|
412
|
+
const configured = normalizeRepoDir(githooksDir);
|
|
413
|
+
const manualFix = `git config ${HOOKS_PATH_KEY} ${configured}`;
|
|
414
|
+
const warnings = [];
|
|
415
|
+
const notes = [];
|
|
416
|
+
const current = readHooksPath(repoRoot);
|
|
417
|
+
if (current.error !== undefined) {
|
|
418
|
+
warnings.push(`${HOOKS_PATH_KEY} could not be read (${current.error}), so it was left alone and the pre-push guard does not run yet: set it with \`${manualFix}\` from the repository root`);
|
|
419
|
+
return { effect: 'unavailable', applied: false, configured, warnings, notes };
|
|
420
|
+
}
|
|
421
|
+
if (current.value !== undefined) {
|
|
422
|
+
if (!sameDirectory(repoRoot, current.value, configured)) {
|
|
423
|
+
warnings.push(`${HOOKS_PATH_KEY} already points at ${current.value}, not at the configured githooksDir ${configured}, so init left it exactly as it is - taking it over would disable whatever hooks you have there. The pre-push guard does not run until the two agree: either point it here with \`${manualFix}\`, or set githooksDir in harness.config.json to ${current.value} and re-run init`);
|
|
424
|
+
return { effect: 'left-alone', applied: false, configured, existing: current.value, warnings, notes };
|
|
425
|
+
}
|
|
426
|
+
notes.push(`${HOOKS_PATH_KEY} already resolves to ${configured}, so it was left as it is`);
|
|
427
|
+
return { effect: 'already-set', applied: false, configured, existing: current.value, warnings, notes };
|
|
428
|
+
}
|
|
429
|
+
if (dryRun) {
|
|
430
|
+
notes.push(`${HOOKS_PATH_KEY} is unset and would be set to ${configured}`);
|
|
431
|
+
return { effect: 'set', applied: false, configured, warnings, notes };
|
|
432
|
+
}
|
|
433
|
+
try {
|
|
434
|
+
// `configured` is the module's only interpolated git argument - safe as an argv element (no
|
|
435
|
+
// shell re-splits it) and already refused by resolveGithooksDir when it names the repo root.
|
|
436
|
+
runGit(['config', HOOKS_PATH_KEY, configured], repoRoot);
|
|
437
|
+
}
|
|
438
|
+
catch (error) {
|
|
439
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
440
|
+
warnings.push(`${HOOKS_PATH_KEY} could not be set (${detail}), so the pre-push guard does not run yet: set it with \`${manualFix}\` from the repository root`);
|
|
441
|
+
return { effect: 'unavailable', applied: false, configured, warnings, notes };
|
|
442
|
+
}
|
|
443
|
+
notes.push(`${HOOKS_PATH_KEY} now points at ${configured}, so the hooks committed there run for this checkout. The value is repo-relative, which git resolves from the top of the working tree the hook runs in, so an additional working copy of this repository resolves it to its own copy of the directory rather than to this one`);
|
|
444
|
+
return { effect: 'set', applied: true, configured, warnings, notes };
|
|
445
|
+
}
|
|
446
|
+
//# sourceMappingURL=githooks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"githooks.js","sourceRoot":"","sources":["../../src/generators/githooks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwFG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAE1C,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAsB,MAAM,oBAAoB,CAAC;AACnF,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAGvD,sGAAsG;AACtG,MAAM,YAAY,GAAG,UAAU,CAAC;AAEhC,2FAA2F;AAC3F,MAAM,CAAC,MAAM,aAAa,GAAG,UAAU,CAAC;AAExC,qEAAqE;AACrE,MAAM,SAAS,GAAG,KAAK,CAAC;AAExB,2EAA2E;AAC3E,MAAM,cAAc,GAAG,gBAAgB,CAAC;AAExC,yFAAyF;AACzF,MAAM,mBAAmB,GAAsB,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC;AAElG,0FAA0F;AAC1F,MAAM,iBAAiB,GAAG,CAAC,CAAC;AAE5B,yEAAyE;AACzE,MAAM,UAAU,GAAG,uBAAuB,CAAC;AAE3C,sDAAsD;AACtD,MAAM,cAAc,GAAG,GAAG,CAAC;AAE3B,0GAA0G;AAC1G,MAAM,WAAW,GAAG,mBAAmB,CAAC;AAExC,gGAAgG;AAChG,MAAM,cAAc,GAAG,GAAG,CAAC;AAE3B;;;;;;;;GAQG;AACH,MAAM,oBAAoB,GAAgE,MAAM,CAAC,MAAM,CAAC;IACtG,CAAC,cAAc,EAAE,kCAAkC,CAAC;IACpD,CAAC,GAAG,EAAE,wCAAwC,CAAC;IAC/C,CAAC,GAAG,EAAE,eAAe,CAAC;IACtB,CAAC,GAAG,EAAE,sBAAsB,CAAC;IAC7B,CAAC,GAAG,EAAE,sBAAsB,CAAC;IAC7B,CAAC,GAAG,EAAE,0BAA0B,CAAC;IACjC,CAAC,GAAG,EAAE,aAAa,CAAC;IACpB,CAAC,GAAG,EAAE,gBAAgB,CAAC;IACvB,CAAC,GAAG,EAAE,gBAAgB,CAAC;IACvB,CAAC,IAAI,EAAE,aAAa,CAAC;IACrB,CAAC,GAAG,EAAE,sBAAsB,CAAC;IAC7B,CAAC,GAAG,EAAE,sBAAsB,CAAC;IAC7B,CAAC,GAAG,EAAE,SAAS,CAAC;IAChB,CAAC,IAAI,EAAE,OAAO,CAAC;IACf,CAAC,IAAI,EAAE,WAAW,CAAC;CACX,CAAC,CAAC;AAEZ;;;GAGG;AACH,MAAM,mBAAmB,GAAsB,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;AAE9E;;;;;;;;;;GAUG;AACH,MAAM,UAAU,aAAa,CAAC,KAAa;IACzC,OAAO,mBAAmB,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;AACtE,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,sBAAsB,CAAC,QAAgB;IACrD,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACnC,MAAM,MAAM,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC;IACrE,IAAI,MAAM,KAAK,CAAC,CAAC;QAAE,OAAO,SAAS,CAAC;IAEpC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC;IACjF,IAAI,KAAK,KAAK,SAAS,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,cAAc,CAAC;QAAE,OAAO,SAAS,CAAC;IAE7E,MAAM,QAAQ,GAAG,KAAK;SACnB,KAAK,CAAC,CAAC,EAAE,CAAC,cAAc,CAAC,MAAM,CAAC;SAChC,IAAI,EAAE;SACN,KAAK,CAAC,cAAc,CAAC;SACrB,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;IACpC,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC;AAC3E,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAwB,EAAE,QAA2B;IACpF,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;IAC/B,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC;IACrC,OAAO,OAAO,CAAC,IAAI,KAAK,UAAU,CAAC,IAAI,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;AACtG,CAAC;AA2ED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAqB;IACtD,MAAM,UAAU,GAAG,gBAAgB,CAAC,CAAC,MAAM,CAAC,WAAW,IAAI,QAAQ,CAAC,WAAW,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IACzF,IAAI,UAAU,KAAK,GAAG,IAAI,UAAU,KAAK,EAAE,EAAE,CAAC;QAC5C,MAAM,IAAI,YAAY,CACpB,kBAAkB,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,WAAW,CAAC,yQAAyQ,CAC9T,CAAC;IACJ,CAAC;IACD,OAAO,UAAU,CAAC;AACpB,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,wBAAwB,CACtC,MAAqB,EACrB,IAA+B;IAE/B,MAAM,UAAU,GAAG,CAAC,MAAM,CAAC,iBAAiB,IAAI,QAAQ,CAAC,iBAAiB,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;IACzG,MAAM,MAAM,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;IACxE,MAAM,aAAa,GAAG,MAAM,CAAC,aAAa,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,EAAE,CAAC;IAEhH,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,IAAI,CACF,mFAAmF,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC,iQAAiQ,eAAe,0HAA0H,aAAa,sHAAsH,eAAe,yIAAyI,aAAa,iFAAiF,CACp3B,CAAC;IACJ,CAAC;IAED,MAAM,QAAQ,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC;IAE1D,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,yBAAyB,CAAC,CAAC,CAAC,eAAe,CAAC;QACtF,KAAK,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,IAAI,oBAAoB,EAAE,CAAC;YACvD,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;gBAAE,SAAS;YACxC,MAAM,IAAI,YAAY,CACpB,OAAO,MAAM,IAAI,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,aAAa,SAAS,gUAAgU,CAC/X,CAAC;QACJ,CAAC;IACH,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,UAAU,CAAC,MAAwC;IAC1D,OAAO,cAAc,CAAC,YAAY,CAAC,GAAG,YAAY,IAAI,aAAa,EAAE,CAAC,EAAE,MAAM,EAAE;QAC9E,QAAQ,EAAE,OAAO,aAAa,WAAW;KAC1C,CAAC,CAAC;AACL,CAAC;AAsBD,8FAA8F;AAC9F,SAAS,mBAAmB,CAAC,QAAgB,EAAE,QAA2B;IACxE,IAAI,QAAgB,CAAC;IACrB,IAAI,CAAC;QACH,QAAQ,GAAG,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC5C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;IAC5B,CAAC;IAED,MAAM,KAAK,GAAG,sBAAsB,CAAC,QAAQ,CAAC,CAAC;IAC/C,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC;IACvD,OAAO,gBAAgB,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AAC1F,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,UAAU,aAAa,CAAC,EAC5B,QAAQ,EACR,MAAM,EACN,IAAI,EACJ,aAAa,GAAG,KAAK,EACrB,MAAM,GAAG,KAAK,GACE;IAChB,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,MAAM,WAAW,GAAG,kBAAkB,CAAC,MAAM,CAAC,CAAC;IAC/C,MAAM,iBAAiB,GAAG,wBAAwB,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;IAChG,MAAM,QAAQ,GAAG,GAAG,WAAW,IAAI,aAAa,EAAE,CAAC;IAEnD,gGAAgG;IAChG,iDAAiD;IACjD,MAAM,OAAO,GAAG,aAAa;QAC3B,CAAC,CAAC,mBAAmB,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,EAAE,iBAAiB,CAAC;QAClE,CAAC,CAAC,SAAS,CAAC;IACd,MAAM,QAAQ,GAAG,OAAO,EAAE,IAAI,KAAK,OAAO,CAAC;IAE3C,IAAI,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,QAAQ,EAAE,WAAW,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,qBAAqB,EAAE,CAAC,CAAC;IACpG,IAAI,CAAC,GAAG,CAAC;QACP,IAAI,EAAE,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC;QAC9B,MAAM,EAAE,kBAAkB;QAC1B,OAAO,EAAE,UAAU,CAAC,EAAE,CAAC,UAAU,CAAC,EAAE,iBAAiB,CAAC,IAAI,CAAC,cAAc,CAAC,EAAE,CAAC;QAC7E,KAAK,EAAE,YAAY,aAAa,EAAE;QAClC,IAAI,EAAE,SAAS;QACf,wFAAwF;QACxF,mFAAmF;QACnF,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,QAAiB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC1D,CAAC,CAAC;IAEH,kGAAkG;IAClG,iGAAiG;IACjG,oGAAoG;IACpG,MAAM,UAAU,GAAG,QAAQ;QACzB,CAAC,CAAC,sEAAsE;QACxE,CAAC,CAAC,yGAAyG,CAAC;IAC9G,KAAK,CAAC,IAAI,CACR,GAAG,QAAQ,yDAAyD,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,ycAAyc,UAAU,oCAAoC,eAAe,iKAAiK,QAAQ,0CAA0C,eAAe,wHAAwH,QAAQ,OAAO,QAAQ,mGAAmG,eAAe,oHAAoH,QAAQ,4EAA4E,CAClxC,CAAC;IAEF,IAAI,OAAO,EAAE,IAAI,KAAK,OAAO,EAAE,CAAC;QAC9B,KAAK,CAAC,IAAI,CACR,MAAM;YACJ,CAAC,CAAC,GAAG,QAAQ,2BAA2B,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,eAAe,oCAAoC,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,0GAA0G,QAAQ,gUAAgU;YAC7kB,CAAC,CAAC,GAAG,QAAQ,2BAA2B,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,oBAAoB,eAAe,aAAa,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,2EAA2E,QAAQ,sOAAsO,CACxc,CAAC;IACJ,CAAC;IAED,8FAA8F;IAC9F,sFAAsF;IACtF,IAAI,OAAO,EAAE,IAAI,KAAK,YAAY,EAAE,CAAC;QACnC,QAAQ,CAAC,IAAI,CACX,GAAG,QAAQ,6KAA6K,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,wEAAwE,eAAe,wOAAwO,QAAQ,4EAA4E,CACxmB,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,iBAAiB,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;AACvE,CAAC;AAED,uFAAuF;AACvF,SAAS,MAAM,CAAC,IAAuB,EAAE,GAAW;IAClD,OAAO,YAAY,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC,EAAE;QACpC,GAAG;QACH,QAAQ,EAAE,MAAM;QAChB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC;QACjC,WAAW,EAAE,IAAI;KAClB,CAAC,CAAC,IAAI,EAAE,CAAC;AACZ,CAAC;AAED,kGAAkG;AAClG,SAAS,aAAa,CAAC,QAAgB;IACrC,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,CAAC,mBAAmB,EAAE,QAAQ,CAAC,CAAC;QACpD,OAAO,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC;IACvC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,OAAO,GAAG,KAAoD,CAAC;QACrE,IAAI,OAAO,CAAC,MAAM,KAAK,iBAAiB;YAAE,OAAO,EAAE,CAAC;QACpD,IAAI,OAAO,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,EAAE,KAAK,EAAE,oBAAoB,EAAE,CAAC;QACtE,OAAO,EAAE,KAAK,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC;IACpC,CAAC;AACH,CAAC;AAED,mGAAmG;AACnG,SAAS,aAAa,CAAC,QAAgB,EAAE,QAAgB,EAAE,UAAkB;IAC3E,OAAO,OAAO,CAAC,QAAQ,EAAE,QAAQ,CAAC,KAAK,OAAO,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;AACvE,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,cAAc,CAAC,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,GAAG,KAAK,EAAoB;IACxF,MAAM,UAAU,GAAG,gBAAgB,CAAC,WAAW,CAAC,CAAC;IACjD,MAAM,SAAS,GAAG,cAAc,cAAc,IAAI,UAAU,EAAE,CAAC;IAC/D,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,MAAM,OAAO,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;IAExC,IAAI,OAAO,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;QAChC,QAAQ,CAAC,IAAI,CACX,GAAG,cAAc,uBAAuB,OAAO,CAAC,KAAK,kFAAkF,SAAS,6BAA6B,CAC9K,CAAC;QACF,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;IAChF,CAAC;IAED,IAAI,OAAO,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;QAChC,IAAI,CAAC,aAAa,CAAC,QAAQ,EAAE,OAAO,CAAC,KAAK,EAAE,UAAU,CAAC,EAAE,CAAC;YACxD,QAAQ,CAAC,IAAI,CACX,GAAG,cAAc,sBAAsB,OAAO,CAAC,KAAK,uCAAuC,UAAU,qLAAqL,SAAS,oDAAoD,OAAO,CAAC,KAAK,kBAAkB,CACvX,CAAC;YACF,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,CAAC,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;QACxG,CAAC;QAED,KAAK,CAAC,IAAI,CAAC,GAAG,cAAc,wBAAwB,UAAU,2BAA2B,CAAC,CAAC;QAC3F,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,CAAC,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;IACzG,CAAC;IAED,IAAI,MAAM,EAAE,CAAC;QACX,KAAK,CAAC,IAAI,CAAC,GAAG,cAAc,iCAAiC,UAAU,EAAE,CAAC,CAAC;QAC3E,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;IACxE,CAAC;IAED,IAAI,CAAC;QACH,4FAA4F;QAC5F,6FAA6F;QAC7F,MAAM,CAAC,CAAC,QAAQ,EAAE,cAAc,EAAE,UAAU,CAAC,EAAE,QAAQ,CAAC,CAAC;IAC3D,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,MAAM,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACtE,QAAQ,CAAC,IAAI,CACX,GAAG,cAAc,sBAAsB,MAAM,4DAA4D,SAAS,6BAA6B,CAChJ,CAAC;QACF,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;IAChF,CAAC;IAED,KAAK,CAAC,IAAI,CACR,GAAG,cAAc,kBAAkB,UAAU,+QAA+Q,CAC7T,CAAC;IACF,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;AACvE,CAAC"}
|