alissa-tools-github-devloop 0.8.27__tar.gz → 0.8.29__tar.gz
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.
- {alissa_tools_github_devloop-0.8.27/src/main/alissa_tools_github_devloop.egg-info → alissa_tools_github_devloop-0.8.29}/PKG-INFO +1 -1
- {alissa_tools_github_devloop-0.8.27 → alissa_tools_github_devloop-0.8.29}/src/main/alissa/tools/github/devloop/config.py +113 -3
- {alissa_tools_github_devloop-0.8.27 → alissa_tools_github_devloop-0.8.29}/src/main/alissa/tools/github/devloop/loop.py +64 -5
- alissa_tools_github_devloop-0.8.29/src/main/alissa/tools/github/devloop/version +1 -0
- {alissa_tools_github_devloop-0.8.27 → alissa_tools_github_devloop-0.8.29/src/main/alissa_tools_github_devloop.egg-info}/PKG-INFO +1 -1
- alissa_tools_github_devloop-0.8.27/src/main/alissa/tools/github/devloop/version +0 -1
- {alissa_tools_github_devloop-0.8.27 → alissa_tools_github_devloop-0.8.29}/LICENSE +0 -0
- {alissa_tools_github_devloop-0.8.27 → alissa_tools_github_devloop-0.8.29}/MANIFEST.in +0 -0
- {alissa_tools_github_devloop-0.8.27 → alissa_tools_github_devloop-0.8.29}/NOTICE +0 -0
- {alissa_tools_github_devloop-0.8.27 → alissa_tools_github_devloop-0.8.29}/README.md +0 -0
- {alissa_tools_github_devloop-0.8.27 → alissa_tools_github_devloop-0.8.29}/requirements.txt +0 -0
- {alissa_tools_github_devloop-0.8.27 → alissa_tools_github_devloop-0.8.29}/setup.cfg +0 -0
- {alissa_tools_github_devloop-0.8.27 → alissa_tools_github_devloop-0.8.29}/setup.py +0 -0
- {alissa_tools_github_devloop-0.8.27 → alissa_tools_github_devloop-0.8.29}/src/main/alissa/tools/github/devloop/__init__.py +0 -0
- {alissa_tools_github_devloop-0.8.27 → alissa_tools_github_devloop-0.8.29}/src/main/alissa/tools/github/devloop/__main__.py +0 -0
- {alissa_tools_github_devloop-0.8.27 → alissa_tools_github_devloop-0.8.29}/src/main/alissa/tools/github/devloop/alissa.py +0 -0
- {alissa_tools_github_devloop-0.8.27 → alissa_tools_github_devloop-0.8.29}/src/main/alissa/tools/github/devloop/alissa_client.py +0 -0
- {alissa_tools_github_devloop-0.8.27 → alissa_tools_github_devloop-0.8.29}/src/main/alissa/tools/github/devloop/bows.py +0 -0
- {alissa_tools_github_devloop-0.8.27 → alissa_tools_github_devloop-0.8.29}/src/main/alissa/tools/github/devloop/fleet_vitals.py +0 -0
- {alissa_tools_github_devloop-0.8.27 → alissa_tools_github_devloop-0.8.29}/src/main/alissa/tools/github/devloop/ghclient.py +0 -0
- {alissa_tools_github_devloop-0.8.27 → alissa_tools_github_devloop-0.8.29}/src/main/alissa/tools/github/devloop/hubs.py +0 -0
- {alissa_tools_github_devloop-0.8.27 → alissa_tools_github_devloop-0.8.29}/src/main/alissa/tools/github/devloop/janitor.py +0 -0
- {alissa_tools_github_devloop-0.8.27 → alissa_tools_github_devloop-0.8.29}/src/main/alissa/tools/github/devloop/loop_events.py +0 -0
- {alissa_tools_github_devloop-0.8.27 → alissa_tools_github_devloop-0.8.29}/src/main/alissa/tools/github/devloop/proc.py +0 -0
- {alissa_tools_github_devloop-0.8.27 → alissa_tools_github_devloop-0.8.29}/src/main/alissa/tools/github/devloop/prompts.py +0 -0
- {alissa_tools_github_devloop-0.8.27 → alissa_tools_github_devloop-0.8.29}/src/main/alissa/tools/github/devloop/state.py +0 -0
- {alissa_tools_github_devloop-0.8.27 → alissa_tools_github_devloop-0.8.29}/src/main/alissa/tools/github/devloop/version.py +0 -0
- {alissa_tools_github_devloop-0.8.27 → alissa_tools_github_devloop-0.8.29}/src/main/alissa/tools/github/devloop/webui/__init__.py +0 -0
- {alissa_tools_github_devloop-0.8.27 → alissa_tools_github_devloop-0.8.29}/src/main/alissa/tools/github/devloop/webui/__main__.py +0 -0
- {alissa_tools_github_devloop-0.8.27 → alissa_tools_github_devloop-0.8.29}/src/main/alissa/tools/github/devloop/webui/auth.py +0 -0
- {alissa_tools_github_devloop-0.8.27 → alissa_tools_github_devloop-0.8.29}/src/main/alissa/tools/github/devloop/webui/page.py +0 -0
- {alissa_tools_github_devloop-0.8.27 → alissa_tools_github_devloop-0.8.29}/src/main/alissa/tools/github/devloop/webui/server.py +0 -0
- {alissa_tools_github_devloop-0.8.27 → alissa_tools_github_devloop-0.8.29}/src/main/alissa/tools/github/devloop/webui/sources.py +0 -0
- {alissa_tools_github_devloop-0.8.27 → alissa_tools_github_devloop-0.8.29}/src/main/alissa/tools/github/devloop/webui/sysinfo.py +0 -0
- {alissa_tools_github_devloop-0.8.27 → alissa_tools_github_devloop-0.8.29}/src/main/alissa_tools_github_devloop.egg-info/SOURCES.txt +0 -0
- {alissa_tools_github_devloop-0.8.27 → alissa_tools_github_devloop-0.8.29}/src/main/alissa_tools_github_devloop.egg-info/dependency_links.txt +0 -0
- {alissa_tools_github_devloop-0.8.27 → alissa_tools_github_devloop-0.8.29}/src/main/alissa_tools_github_devloop.egg-info/entry_points.txt +0 -0
- {alissa_tools_github_devloop-0.8.27 → alissa_tools_github_devloop-0.8.29}/src/main/alissa_tools_github_devloop.egg-info/top_level.txt +0 -0
|
@@ -26,6 +26,10 @@ through to the config rather than fail the daemon's boot on a mode of `''`.
|
|
|
26
26
|
`fleet_vitals_enabled` through `ALISSA_DEV_FLEET_VITALS_ENABLED` likewise.
|
|
27
27
|
`revert_label` rides `ALISSA_REVERT_LABEL` the same way (issue #131): a
|
|
28
28
|
non-blank value wins over the CLI and the file, a blank one falls through.
|
|
29
|
+
|
|
30
|
+
`regenerate` (issue #138) is env-only: `ALISSA_DEV_REGENERATE` maps
|
|
31
|
+
repo-scoped globs to the command that regenerates them, and the daemon only
|
|
32
|
+
ever RENDERS that map into a worker's directive -- it never runs the command.
|
|
29
33
|
"""
|
|
30
34
|
|
|
31
35
|
from __future__ import annotations
|
|
@@ -116,6 +120,94 @@ ENV_FLEET_VITALS = "ALISSA_DEV_FLEET_VITALS_ENABLED"
|
|
|
116
120
|
# it (no `_DEV_` infix), because that is the name the operator runbook gives.
|
|
117
121
|
ENV_REVERT_LABEL = "ALISSA_REVERT_LABEL"
|
|
118
122
|
|
|
123
|
+
# The regenerable-paths map (issue #138): `owner/repo:<glob>=<command>`
|
|
124
|
+
# entries, `|`-separated (the daemon-family list separator -- a command may
|
|
125
|
+
# itself carry `,`, `;` or spaces, so `|` is the ONLY separator here), default
|
|
126
|
+
# empty. Env-only, read straight from the environment like ENV_REVERT_LABEL:
|
|
127
|
+
# not a config key, not a build ARG, not written by the config generator. Each
|
|
128
|
+
# entry is validated at load and a malformed one is refused by NAME -- the
|
|
129
|
+
# variable's and the entry's -- because the failure mode of a silently
|
|
130
|
+
# dropped entry is a worker hand-merging a generated file a poll later. The
|
|
131
|
+
# command is rendered into the worker's directive and runs INSIDE the
|
|
132
|
+
# worker's worktree as part of the session; the daemon never executes it.
|
|
133
|
+
ENV_REGENERATE = "ALISSA_DEV_REGENERATE"
|
|
134
|
+
|
|
135
|
+
# `owner/repo` for a regenerate entry: exactly one `/`, no whitespace, and no
|
|
136
|
+
# `:` (the separator that ends it). Shape only, like the actor-id check.
|
|
137
|
+
_REGENERATE_REPO_RE = re.compile(r"^[^\s/:]+/[^\s/:]+\Z")
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@dataclass(frozen=True)
|
|
141
|
+
class RegenerateRule:
|
|
142
|
+
"""One `ALISSA_DEV_REGENERATE` entry: files under `glob` in `repo` are a
|
|
143
|
+
pure function of the code, regenerated by `command` (run in the worker's
|
|
144
|
+
worktree, never by the daemon). `repo` keeps the operator's casing;
|
|
145
|
+
matching casefolds, as `Config.watches` does."""
|
|
146
|
+
|
|
147
|
+
repo: str
|
|
148
|
+
glob: str
|
|
149
|
+
command: str
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def parse_regenerate(raw: str) -> "tuple[RegenerateRule, ...]":
|
|
153
|
+
"""Parse the ALISSA_DEV_REGENERATE value into rules, refusing malformed
|
|
154
|
+
entries by name.
|
|
155
|
+
|
|
156
|
+
The grammar is `owner/repo:<glob>=<command>` per entry, split on `|`
|
|
157
|
+
ONLY: the repo ends at the first `:`, the glob at the first `=`, and the
|
|
158
|
+
command is everything after it (so a command may carry `=` and `:`, and a
|
|
159
|
+
glob may carry `:` but not `=`). Whitespace around an entry and its three
|
|
160
|
+
parts is stripped; empty entries (a trailing `|`) are dropped, so a blank
|
|
161
|
+
or unset variable is the empty map -- today's behaviour exactly. An
|
|
162
|
+
entry with a missing part, a repo that is not `owner/repo`, or the same
|
|
163
|
+
repo+glob given twice with different commands is a ValueError naming
|
|
164
|
+
ENV_REGENERATE and the entry: a dropped entry would surface a poll later
|
|
165
|
+
as a hand-merged generated file, which is the very thing the map exists
|
|
166
|
+
to prevent. Exact duplicates collapse.
|
|
167
|
+
"""
|
|
168
|
+
rules: "list[RegenerateRule]" = []
|
|
169
|
+
seen: "dict[tuple[str, str], RegenerateRule]" = {}
|
|
170
|
+
for part in (raw or "").split("|"):
|
|
171
|
+
entry = part.strip()
|
|
172
|
+
if not entry:
|
|
173
|
+
continue
|
|
174
|
+
|
|
175
|
+
def refuse(problem: str) -> ValueError:
|
|
176
|
+
return ValueError(
|
|
177
|
+
f"{ENV_REGENERATE} entry {entry!r} is malformed: {problem} "
|
|
178
|
+
f"(expected owner/repo:<glob>=<command>, entries separated "
|
|
179
|
+
f"by '|')"
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
repo, colon, rest = entry.partition(":")
|
|
183
|
+
if not colon:
|
|
184
|
+
raise refuse("missing ':' between the repo and the glob")
|
|
185
|
+
repo = repo.strip()
|
|
186
|
+
if not _REGENERATE_REPO_RE.match(repo):
|
|
187
|
+
raise refuse(f"repo {repo!r} is not of the form owner/repo")
|
|
188
|
+
glob, equals, command = rest.partition("=")
|
|
189
|
+
if not equals:
|
|
190
|
+
raise refuse("missing '=' between the glob and the command")
|
|
191
|
+
glob = glob.strip()
|
|
192
|
+
command = command.strip()
|
|
193
|
+
if not glob:
|
|
194
|
+
raise refuse("the glob is empty")
|
|
195
|
+
if not command:
|
|
196
|
+
raise refuse("the command is empty")
|
|
197
|
+
rule = RegenerateRule(repo=repo, glob=glob, command=command)
|
|
198
|
+
key = (repo.casefold(), glob)
|
|
199
|
+
prior = seen.get(key)
|
|
200
|
+
if prior is not None:
|
|
201
|
+
if prior.command != command:
|
|
202
|
+
raise refuse(
|
|
203
|
+
f"{repo}:{glob} is already mapped to {prior.command!r}"
|
|
204
|
+
)
|
|
205
|
+
continue
|
|
206
|
+
seen[key] = rule
|
|
207
|
+
rules.append(rule)
|
|
208
|
+
return tuple(rules)
|
|
209
|
+
|
|
210
|
+
|
|
119
211
|
# The prompt responder's master switch (issue #127): `on` answers, `observe`
|
|
120
212
|
# classifies and narrates but sends nothing, `off` never captures a pane.
|
|
121
213
|
PROMPT_ON = "on"
|
|
@@ -744,6 +836,12 @@ class Config:
|
|
|
744
836
|
# to act (at the incident's growth rate, ~2 days). 0 never warns.
|
|
745
837
|
disk_warn_percent: int = 80
|
|
746
838
|
|
|
839
|
+
# The regenerable-paths map (issue #138), from ALISSA_DEV_REGENERATE only
|
|
840
|
+
# (no config key, no CLI flag). Empty by default, and empty means every
|
|
841
|
+
# worker directive renders byte-identical to a build without the map.
|
|
842
|
+
# Read through `regenerate_for(owner, repo)`, never executed here.
|
|
843
|
+
regenerate: tuple[RegenerateRule, ...] = ()
|
|
844
|
+
|
|
747
845
|
dry_run: bool = False
|
|
748
846
|
|
|
749
847
|
# Derived at build time: `repos` casefolded for matching. GitHub names are
|
|
@@ -875,6 +973,13 @@ class Config:
|
|
|
875
973
|
and gets an answer that includes the enrolled repos."""
|
|
876
974
|
return full_name.casefold() in self._repo_match
|
|
877
975
|
|
|
976
|
+
def regenerate_for(self, owner: str, repo: str) -> "tuple[RegenerateRule, ...]":
|
|
977
|
+
"""The regenerate rules scoped to `owner/repo`, in the order the
|
|
978
|
+
operator wrote them; empty for a repo with no entry. Casefolded like
|
|
979
|
+
`watches`: GitHub names are case-insensitive."""
|
|
980
|
+
wanted = f"{owner}/{repo}".casefold()
|
|
981
|
+
return tuple(r for r in self.regenerate if r.repo.casefold() == wanted)
|
|
982
|
+
|
|
878
983
|
def trusts_feed_owner(self, actor_id: "str | None") -> bool:
|
|
879
984
|
"""May a Body of Work owned by this actor act as a repo feed?
|
|
880
985
|
|
|
@@ -900,9 +1005,10 @@ class Config:
|
|
|
900
1005
|
"""Merge the layers and validate. `overrides` entries that are None mean
|
|
901
1006
|
"not specified on the CLI" and fall through to the file / defaults.
|
|
902
1007
|
|
|
903
|
-
`environ` is only read for
|
|
904
|
-
|
|
905
|
-
|
|
1008
|
+
`environ` is only read for the env rails (`ALISSA_DEV_REPOS_SOURCE`
|
|
1009
|
+
and its siblings, see the module docstring, plus the env-only
|
|
1010
|
+
`ALISSA_DEV_REGENERATE` map); it defaults to the real environment and
|
|
1011
|
+
is a parameter so a test never has to monkeypatch it."""
|
|
906
1012
|
raw: dict[str, Any] = dict(file_data or {})
|
|
907
1013
|
|
|
908
1014
|
if "workspace_root" in raw:
|
|
@@ -994,6 +1100,9 @@ class Config:
|
|
|
994
1100
|
revert_label = env_revert_label
|
|
995
1101
|
if not revert_label.strip():
|
|
996
1102
|
raise ValueError("revert_label must not be blank")
|
|
1103
|
+
# The regenerable-paths map (issue #138): env-only, validated here so
|
|
1104
|
+
# a malformed entry fails the boot by name instead of a directive.
|
|
1105
|
+
regenerate = parse_regenerate(env_map.get(ENV_REGENERATE, ""))
|
|
997
1106
|
|
|
998
1107
|
bow_owners = normalize_bow_owners(raw.get("bow_owners", cls.bow_owners))
|
|
999
1108
|
|
|
@@ -1284,6 +1393,7 @@ class Config:
|
|
|
1284
1393
|
raw.get("cache_paths", cls.cache_paths), "cache_paths"
|
|
1285
1394
|
),
|
|
1286
1395
|
disk_warn_percent=warn_percent,
|
|
1396
|
+
regenerate=regenerate,
|
|
1287
1397
|
dry_run=bool(raw.get("dry_run", cls.dry_run)),
|
|
1288
1398
|
)
|
|
1289
1399
|
|
|
@@ -458,7 +458,7 @@ from collections import Counter
|
|
|
458
458
|
from dataclasses import dataclass, replace
|
|
459
459
|
from enum import Enum
|
|
460
460
|
from pathlib import Path
|
|
461
|
-
from typing import Callable
|
|
461
|
+
from typing import Callable, Sequence
|
|
462
462
|
|
|
463
463
|
from . import janitor
|
|
464
464
|
from . import fleet_vitals as fleet_vitals_mod
|
|
@@ -486,6 +486,7 @@ from .config import (
|
|
|
486
486
|
PROMPT_OFF,
|
|
487
487
|
REPOS_BOWS,
|
|
488
488
|
Config,
|
|
489
|
+
RegenerateRule,
|
|
489
490
|
)
|
|
490
491
|
from .ghclient import (
|
|
491
492
|
FIRST_PARENT_MAX_PAGES,
|
|
@@ -771,6 +772,49 @@ _SHELL_RULE = (
|
|
|
771
772
|
"change the command; do not retry it. "
|
|
772
773
|
)
|
|
773
774
|
|
|
775
|
+
# The regenerate-on-conflict rule (issue #138), rendered into every worker
|
|
776
|
+
# directive that can end up merging main into its branch -- develop, resume,
|
|
777
|
+
# fix, fix-resume and maintain -- and ONLY for a repo with entries in
|
|
778
|
+
# ALISSA_DEV_REGENERATE: `render_regenerate_rule` returns "" otherwise, so a
|
|
779
|
+
# repo without entries (and a deployment without the variable) renders every
|
|
780
|
+
# directive byte-identical to before the rule existed. A generated file is a
|
|
781
|
+
# pure function of the code; a hand-merge of it usually works and is still
|
|
782
|
+
# the wrong operation (the repo's generated-drift CI job fails any hand-merge
|
|
783
|
+
# that does not match, at the cost of a review round), so the rule is: take
|
|
784
|
+
# either side, finish the merge, rerun the generator, commit. The command
|
|
785
|
+
# runs in the worker's worktree as part of the session -- the daemon never
|
|
786
|
+
# executes it, it only spells it here.
|
|
787
|
+
_REGENERATE_RULE_ONE = (
|
|
788
|
+
"If merging main conflicts inside `{glob}`, do not hand-merge it: "
|
|
789
|
+
"`git checkout --theirs -- <paths>` (or `--ours` — either side), finish "
|
|
790
|
+
"the merge, run `{command}`, and commit the regenerated files in the "
|
|
791
|
+
"same push; the repo's CI verifies them. "
|
|
792
|
+
)
|
|
793
|
+
_REGENERATE_RULE_MANY = (
|
|
794
|
+
"If merging main conflicts inside {paths}, do not hand-merge it: "
|
|
795
|
+
"`git checkout --theirs -- <paths>` (or `--ours` — either side), finish "
|
|
796
|
+
"the merge, run that path's regenerate command, and commit the "
|
|
797
|
+
"regenerated files in the same push; the repo's CI verifies them. "
|
|
798
|
+
)
|
|
799
|
+
|
|
800
|
+
|
|
801
|
+
def render_regenerate_rule(rules: "Sequence[RegenerateRule]") -> str:
|
|
802
|
+
"""The rule for one repo's ALISSA_DEV_REGENERATE entries, or "" when it
|
|
803
|
+
has none. One entry renders the issue's own sentence with the glob and the
|
|
804
|
+
command substituted; several render one sentence naming each glob with
|
|
805
|
+
its command. Always ends in a space so it splices between two sentences
|
|
806
|
+
of a directive, and its placeholder renders to nothing when empty."""
|
|
807
|
+
if not rules:
|
|
808
|
+
return ""
|
|
809
|
+
if len(rules) == 1:
|
|
810
|
+
return _REGENERATE_RULE_ONE.format(
|
|
811
|
+
glob=rules[0].glob, command=rules[0].command
|
|
812
|
+
)
|
|
813
|
+
named = [f"`{r.glob}` (regenerate with `{r.command}`)" for r in rules]
|
|
814
|
+
paths = ", ".join(named[:-1]) + " or " + named[-1]
|
|
815
|
+
return _REGENERATE_RULE_MANY.format(paths=paths)
|
|
816
|
+
|
|
817
|
+
|
|
774
818
|
DEV_DIRECTIVE = (
|
|
775
819
|
"You are an IMPLEMENTER, not a reviewer. Implement GitHub issue "
|
|
776
820
|
"{issue_url} (attempt {attempt} of {cap}) — the issue is your "
|
|
@@ -786,7 +830,8 @@ DEV_DIRECTIVE = (
|
|
|
786
830
|
+ _BRANCH_RULE
|
|
787
831
|
+ _SHELL_RULE
|
|
788
832
|
+ "Implement to the issue's acceptance criteria and VERIFY locally (run the "
|
|
789
|
-
"repo's test/style/type scripts) before publishing.
|
|
833
|
+
"repo's test/style/type scripts) before publishing. {regenerate_rule}"
|
|
834
|
+
"Open a DRAFT PR whose "
|
|
790
835
|
"body contains `Closes #{number}` and BOTH task refs — the origin task "
|
|
791
836
|
"AND your implementation task: the PR body is the canonical cross-actor "
|
|
792
837
|
"record, and each actor's tasks live in its own partition — and attach "
|
|
@@ -845,7 +890,8 @@ RESUME_DIRECTIVE = (
|
|
|
845
890
|
"are unchanged and still authoritative — and VERIFY locally (run the "
|
|
846
891
|
"repo's test/style/type scripts) before publishing. NEVER force-push: "
|
|
847
892
|
"history stays append-only, or the PR's review threads lose their "
|
|
848
|
-
"anchors.
|
|
893
|
+
"anchors. {regenerate_rule}"
|
|
894
|
+
"Keep the PR body carrying `Closes #{number}` and BOTH task refs "
|
|
849
895
|
"(the origin task AND your implementation task) — restoring them if the "
|
|
850
896
|
"dead session did not. "
|
|
851
897
|
+ _SHELL_RULE
|
|
@@ -906,7 +952,7 @@ FIX_DIRECTIVE = (
|
|
|
906
952
|
+ "FIRST verify the triage of every prior review finding per CR8: exactly "
|
|
907
953
|
"one reasoned `[triage:*]` reply on each finding's thread — post any that "
|
|
908
954
|
"are missing. Then fix the pursued findings on the same branch and VERIFY "
|
|
909
|
-
"locally (run the repo's test/style/type scripts). "
|
|
955
|
+
"locally (run the repo's test/style/type scripts). {regenerate_rule}"
|
|
910
956
|
+ _NEWER_ROUNDS
|
|
911
957
|
+ _FIX_DELIVERY_NOTE
|
|
912
958
|
+ " "
|
|
@@ -964,7 +1010,7 @@ FIX_RESUME_DIRECTIVE = (
|
|
|
964
1010
|
"thread it already answered. Then fix the pursued findings, completing "
|
|
965
1011
|
"the ORIGINAL acceptance criteria the issue set, and VERIFY locally (run "
|
|
966
1012
|
"the repo's test/style/type scripts). NEVER force-push: history stays "
|
|
967
|
-
"append-only, or the review threads lose their anchors. "
|
|
1013
|
+
"append-only, or the review threads lose their anchors. {regenerate_rule}"
|
|
968
1014
|
+ _NEWER_ROUNDS
|
|
969
1015
|
+ _FIX_DELIVERY_NOTE
|
|
970
1016
|
+ " "
|
|
@@ -1015,6 +1061,7 @@ MAINTAIN_DIRECTIVE = (
|
|
|
1015
1061
|
"while history is append-only — resolving any conflicts so BOTH sides' "
|
|
1016
1062
|
"intent is preserved, then run the repo's four scripts (unit tests, "
|
|
1017
1063
|
"coverage, style, types) and get ALL of them green before publishing. "
|
|
1064
|
+
"{regenerate_rule}"
|
|
1018
1065
|
+ _REVIEW_SEAT
|
|
1019
1066
|
+ "The terminal sequence's ORDER is load-bearing: push the merge commit, "
|
|
1020
1067
|
"THEN re-request review from {reviewers} — the push stales any approve, "
|
|
@@ -3680,6 +3727,7 @@ class DevWatcher:
|
|
|
3680
3727
|
"none resolved — follow the alissa-pr-review skill's reviewer "
|
|
3681
3728
|
"resolution"
|
|
3682
3729
|
)
|
|
3730
|
+
regenerate_rule = self._regenerate_rule(issue.owner, issue.repo)
|
|
3683
3731
|
if resume_pr is not None:
|
|
3684
3732
|
directive = RESUME_DIRECTIVE.format(
|
|
3685
3733
|
issue_url=issue.url,
|
|
@@ -3693,6 +3741,7 @@ class DevWatcher:
|
|
|
3693
3741
|
head_ref=resume_pr.head_ref,
|
|
3694
3742
|
worktree_dir=worktree_dir(resume_pr.head_ref),
|
|
3695
3743
|
dead_session=dead_session,
|
|
3744
|
+
regenerate_rule=regenerate_rule,
|
|
3696
3745
|
)
|
|
3697
3746
|
else:
|
|
3698
3747
|
directive = DEV_DIRECTIVE.format(
|
|
@@ -3703,6 +3752,7 @@ class DevWatcher:
|
|
|
3703
3752
|
task_instruction=task_instruction,
|
|
3704
3753
|
reviewers=reviewers,
|
|
3705
3754
|
session=name,
|
|
3755
|
+
regenerate_rule=regenerate_rule,
|
|
3706
3756
|
)
|
|
3707
3757
|
|
|
3708
3758
|
# assign_self FIRST: the assignment is the server-side in-flight
|
|
@@ -6802,6 +6852,7 @@ class DevWatcher:
|
|
|
6802
6852
|
reviewer=review.author,
|
|
6803
6853
|
session=name,
|
|
6804
6854
|
dead_session=dead_session,
|
|
6855
|
+
regenerate_rule=self._regenerate_rule(pr.owner, pr.repo),
|
|
6805
6856
|
)
|
|
6806
6857
|
|
|
6807
6858
|
# BEFORE the enqueue (issue #53), for the reason _spawn spells out:
|
|
@@ -7065,6 +7116,7 @@ class DevWatcher:
|
|
|
7065
7116
|
worktree_dir=worktree_dir(pr.head_ref),
|
|
7066
7117
|
reviewers=reviewers,
|
|
7067
7118
|
session=name,
|
|
7119
|
+
regenerate_rule=self._regenerate_rule(pr.owner, pr.repo),
|
|
7068
7120
|
)
|
|
7069
7121
|
|
|
7070
7122
|
# BEFORE the enqueue (issue #53), for the reason _spawn spells out:
|
|
@@ -7685,6 +7737,13 @@ class DevWatcher:
|
|
|
7685
7737
|
if self._append_activity(owner, repo, number, line, display_slug):
|
|
7686
7738
|
self.state.record_activity(slug_key, number, kind)
|
|
7687
7739
|
|
|
7740
|
+
def _regenerate_rule(self, owner: str, repo: str) -> str:
|
|
7741
|
+
"""The regenerate-on-conflict clause for this repo's directives
|
|
7742
|
+
(issue #138): "" -- and so a byte-identical directive -- unless
|
|
7743
|
+
ALISSA_DEV_REGENERATE names `owner/repo`. Shared by all five worker
|
|
7744
|
+
directives. Rendered only: the command is the worker's to run."""
|
|
7745
|
+
return render_regenerate_rule(self.config.regenerate_for(owner, repo))
|
|
7746
|
+
|
|
7688
7747
|
def _ensure_hub(self, owner: str, repo: str) -> tuple[Path, str | None]:
|
|
7689
7748
|
"""Resolve a spawn's cwd (the hub ROOT), hub-ifying the repo first if
|
|
7690
7749
|
configured. Shared by all three edges (issue, fix, and maintenance
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.8.29
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0.8.27
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|