code-context-control 2.82.0__py3-none-any.whl → 2.85.0__py3-none-any.whl
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.
- cli/c3.py +15 -2
- cli/commands/parser.py +27 -0
- cli/mcp_server.py +20 -3
- cli/tools/ci.py +126 -3
- {code_context_control-2.82.0.dist-info → code_context_control-2.85.0.dist-info}/METADATA +1 -1
- {code_context_control-2.82.0.dist-info → code_context_control-2.85.0.dist-info}/RECORD +17 -13
- services/ci_act.py +36 -0
- services/ci_cache.py +345 -0
- services/ci_github.py +161 -0
- services/ci_impact.py +269 -0
- services/ci_intel.py +188 -0
- services/ci_runner.py +138 -5
- services/ci_workflow.py +1 -1
- {code_context_control-2.82.0.dist-info → code_context_control-2.85.0.dist-info}/WHEEL +0 -0
- {code_context_control-2.82.0.dist-info → code_context_control-2.85.0.dist-info}/entry_points.txt +0 -0
- {code_context_control-2.82.0.dist-info → code_context_control-2.85.0.dist-info}/licenses/LICENSE +0 -0
- {code_context_control-2.82.0.dist-info → code_context_control-2.85.0.dist-info}/top_level.txt +0 -0
cli/c3.py
CHANGED
|
@@ -92,7 +92,7 @@ console = Console() if HAS_RICH else None
|
|
|
92
92
|
# Config
|
|
93
93
|
CONFIG_DIR = ".c3"
|
|
94
94
|
CONFIG_FILE = ".c3/config.json"
|
|
95
|
-
__version__ = "2.
|
|
95
|
+
__version__ = "2.85.0"
|
|
96
96
|
|
|
97
97
|
|
|
98
98
|
def _compress_file_cli(compressor, path, mode="smart", **kw):
|
|
@@ -6055,6 +6055,15 @@ def _ci_main(args) -> int:
|
|
|
6055
6055
|
from cli.tools.ci import handle_ci
|
|
6056
6056
|
|
|
6057
6057
|
sub = getattr(args, "ci_cmd", None) or "inspect"
|
|
6058
|
+
if sub == "publish":
|
|
6059
|
+
if getattr(args, "dry_run", False):
|
|
6060
|
+
args.job = "dry-run"
|
|
6061
|
+
if getattr(args, "force", False):
|
|
6062
|
+
args.allow_side_effects = True
|
|
6063
|
+
if sub == "cache" and getattr(args, "clear", False):
|
|
6064
|
+
# `--clear` is a flag on the CLI; the shared handler takes it
|
|
6065
|
+
# positionally so both surfaces route through one code path.
|
|
6066
|
+
args.job = "clear"
|
|
6058
6067
|
project_path = getattr(args, "project_path", ".") or "."
|
|
6059
6068
|
as_json = bool(getattr(args, "json", False))
|
|
6060
6069
|
|
|
@@ -6106,7 +6115,11 @@ def _ci_main(args) -> int:
|
|
|
6106
6115
|
getattr(args, "event", "") or "",
|
|
6107
6116
|
getattr(args, "engine", "auto") or "auto",
|
|
6108
6117
|
bool(getattr(args, "allow_side_effects", False)),
|
|
6109
|
-
getattr(args, "network", "") or ""
|
|
6118
|
+
getattr(args, "network", "") or "",
|
|
6119
|
+
"required" if getattr(args, "required", False) else "full",
|
|
6120
|
+
getattr(args, "base", "") or "",
|
|
6121
|
+
bool(getattr(args, "allow_host_mutation", False)),
|
|
6122
|
+
bool(getattr(args, "no_cache", False)))
|
|
6110
6123
|
print(out)
|
|
6111
6124
|
# Exit code is the finding: a non-full verdict must not read as success to
|
|
6112
6125
|
# a shell script or a pre-push hook.
|
cli/commands/parser.py
CHANGED
|
@@ -507,6 +507,17 @@ def build_parser(version: str, parse_cli_ide_arg):
|
|
|
507
507
|
ci_run.add_argument("--allow-side-effects", action="store_true",
|
|
508
508
|
help="Let the act engine run jobs that look like they "
|
|
509
509
|
"publish or deploy. Refused by default.")
|
|
510
|
+
ci_run.add_argument("--required", action="store_true",
|
|
511
|
+
help="Run only the jobs a change could have broken "
|
|
512
|
+
"(conservative; see `c3 ci plan`)")
|
|
513
|
+
ci_run.add_argument("--base", default="",
|
|
514
|
+
help="Diff against this ref instead of the working tree")
|
|
515
|
+
ci_run.add_argument("--allow-host-mutation", action="store_true",
|
|
516
|
+
help="Permit native steps that reconfigure this machine "
|
|
517
|
+
"(pip/npm -g/apt install). Refused by default.")
|
|
518
|
+
ci_run.add_argument("--no-cache", action="store_true",
|
|
519
|
+
help="Ignore cached results and execute every "
|
|
520
|
+
"selected job")
|
|
510
521
|
ci_run.add_argument("--network", default="",
|
|
511
522
|
help="Container network for the act engine (e.g. `none` "
|
|
512
523
|
"to cut egress). Default: act's own.")
|
|
@@ -530,6 +541,22 @@ def build_parser(version: str, parse_cli_ide_arg):
|
|
|
530
541
|
ci_logs.add_argument("--tail", type=int, default=200)
|
|
531
542
|
|
|
532
543
|
_ci_common(ci_subs.add_parser("runs", help="Recent local CI runs"))
|
|
544
|
+
ci_plan = _ci_common(ci_subs.add_parser(
|
|
545
|
+
"plan", help="Show which jobs a change requires, and why"))
|
|
546
|
+
ci_plan.add_argument("--base", default="",
|
|
547
|
+
help="Diff against this ref instead of the working tree")
|
|
548
|
+
ci_pub = _ci_common(ci_subs.add_parser(
|
|
549
|
+
"publish", help="Post the last run as a GitHub commit status"))
|
|
550
|
+
ci_pub.add_argument("--run", dest="run_id", default="")
|
|
551
|
+
ci_pub.add_argument("--dry-run", action="store_true")
|
|
552
|
+
ci_pub.add_argument("--force", action="store_true",
|
|
553
|
+
help="Publish a PARTIAL result as `pending`")
|
|
554
|
+
_ci_common(ci_subs.add_parser(
|
|
555
|
+
"history", help="Per-job pass/fail history and flake detection"))
|
|
556
|
+
ci_cache_p = _ci_common(ci_subs.add_parser(
|
|
557
|
+
"cache", help="Cached-result store: size, or --clear it"))
|
|
558
|
+
ci_cache_p.add_argument("--clear", action="store_true",
|
|
559
|
+
help="Drop every cached result and dependency cache")
|
|
533
560
|
_ci_common(ci_subs.add_parser(
|
|
534
561
|
"doctor", help="Which execution engines are available on this machine"))
|
|
535
562
|
|
cli/mcp_server.py
CHANGED
|
@@ -771,10 +771,25 @@ async def c3_ci(action: str = "inspect", job: str = "", run_id: str = "",
|
|
|
771
771
|
allow_foreign: bool = False, workflow: str = "",
|
|
772
772
|
tail: int = 200, timeout: int = 0, event: str = "",
|
|
773
773
|
engine: str = "auto", allow_side_effects: bool = False,
|
|
774
|
-
network: str = "",
|
|
774
|
+
network: str = "", mode: str = "full", base: str = "",
|
|
775
|
+
allow_host_mutation: bool = False,
|
|
776
|
+
no_cache: bool = False, ctx: Context = None) -> str:
|
|
775
777
|
"""RUN THIS REPO'S REAL CI LOCALLY — before pushing, not after.
|
|
776
778
|
Reads .github/workflows/*.yml as the source of truth (no second CI config).
|
|
777
|
-
actions: inspect | run | rerun | status | failures | logs | runs |
|
|
779
|
+
actions: inspect | plan | run | rerun | status | failures | logs | runs |
|
|
780
|
+
cache | history | publish | doctor. publish posts the last run as a
|
|
781
|
+
GitHub COMMIT STATUS via your gh auth; it refuses a dirty tree, an
|
|
782
|
+
unpushed commit, and (unless forced) a PARTIAL result. history reports per-job fail rates and
|
|
783
|
+
flags FLAKY jobs — ones that both passed and failed on identical
|
|
784
|
+
inputs, so the code cannot be the cause. A job whose inputs are unchanged since it last passed
|
|
785
|
+
is reused (status `cached`, nothing executed); no_cache forces it.
|
|
786
|
+
mode: full (default) | required. required runs only the jobs a change
|
|
787
|
+
could have broken; it is CONSERVATIVE — anything unmapped runs. Use
|
|
788
|
+
action='plan' to see the decision and reason for every job first.
|
|
789
|
+
allow_host_mutation: the NATIVE engine refuses steps that would
|
|
790
|
+
reconfigure this machine (pip/npm -g/apt/brew install...). It has no
|
|
791
|
+
isolation; running this repo's own CI natively once uninstalled C3.
|
|
792
|
+
Prefer engine='act'.
|
|
778
793
|
inspect: workflows, the job DAG, and which jobs are runnable on THIS host.
|
|
779
794
|
run: execute in dependency order; job='lint' or 'test (ubuntu-latest, 3.12)'
|
|
780
795
|
selects one (bare job name = all its matrix cells). A job whose dependency
|
|
@@ -798,7 +813,9 @@ async def c3_ci(action: str = "inspect", job: str = "", run_id: str = "",
|
|
|
798
813
|
return await asyncio.to_thread(handle_ci, action, job, run_id,
|
|
799
814
|
allow_foreign, workflow, tail, timeout,
|
|
800
815
|
svc, finalize, event, engine,
|
|
801
|
-
allow_side_effects, network
|
|
816
|
+
allow_side_effects, network,
|
|
817
|
+
mode, base, allow_host_mutation,
|
|
818
|
+
no_cache)
|
|
802
819
|
|
|
803
820
|
|
|
804
821
|
@mcp.tool()
|
cli/tools/ci.py
CHANGED
|
@@ -12,13 +12,14 @@ That distinction is the product.
|
|
|
12
12
|
"""
|
|
13
13
|
from __future__ import annotations
|
|
14
14
|
|
|
15
|
+
from services import ci_impact
|
|
15
16
|
from services import ci_runner as cr
|
|
16
17
|
from services.ci_workflow import inspect_project
|
|
17
18
|
|
|
18
19
|
_MARK = {
|
|
19
20
|
cr.PASSED: "PASS", cr.FAILED: "FAIL", cr.SKIPPED: "SKIP",
|
|
20
21
|
cr.UNSUPPORTED: "UNSUP", cr.FOREIGN: "OTHER-OS", cr.TIMEOUT: "TIMEOUT",
|
|
21
|
-
cr.DESELECTED: "-",
|
|
22
|
+
cr.DESELECTED: "-", cr.CACHED: "CACHED",
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
# Verdicts an agent may treat as "safe to push". Deliberately one item long.
|
|
@@ -81,7 +82,10 @@ def _failure_rows(jobs: list, limit: int = 25) -> list:
|
|
|
81
82
|
def handle_ci(action: str, job: str, run_id: str, allow_foreign: bool,
|
|
82
83
|
workflow: str, tail: int, timeout: int, svc, finalize,
|
|
83
84
|
event: str = "", engine: str = "auto",
|
|
84
|
-
allow_side_effects: bool = False, network: str = ""
|
|
85
|
+
allow_side_effects: bool = False, network: str = "",
|
|
86
|
+
mode: str = "full", base: str = "",
|
|
87
|
+
allow_host_mutation: bool = False,
|
|
88
|
+
no_cache: bool = False) -> str:
|
|
85
89
|
"""Route c3_ci actions."""
|
|
86
90
|
project = str(svc.project_path)
|
|
87
91
|
action = (action or "inspect").strip().lower()
|
|
@@ -152,6 +156,43 @@ def handle_ci(action: str, job: str, run_id: str, allow_foreign: bool,
|
|
|
152
156
|
return finalize("c3_ci", args, "\n".join(lines),
|
|
153
157
|
f"{len(data['jobs'])} jobs, {len(data['runnable'])} runnable")
|
|
154
158
|
|
|
159
|
+
# ── plan (required mode, no execution) ───────────────────────────────
|
|
160
|
+
if action == "plan":
|
|
161
|
+
from services.ci_workflow import (
|
|
162
|
+
build_dag,
|
|
163
|
+
discover_workflows,
|
|
164
|
+
git_context,
|
|
165
|
+
parse_workflow,
|
|
166
|
+
)
|
|
167
|
+
wfs = [parse_workflow(p) for p in discover_workflows(project)]
|
|
168
|
+
dag = build_dag([w for w in wfs if not w.error],
|
|
169
|
+
event=event, git=git_context(project))
|
|
170
|
+
try:
|
|
171
|
+
ordered = dag.topo_order()
|
|
172
|
+
except Exception:
|
|
173
|
+
ordered = dag.instances
|
|
174
|
+
plan = ci_impact.plan_required(project, ordered, wfs, base=base,
|
|
175
|
+
event=event)
|
|
176
|
+
lines = [
|
|
177
|
+
f"Required-mode plan ({len(plan.selected)} run / "
|
|
178
|
+
f"{len(plan.skipped)} skip) vs {base or 'the working tree'}",
|
|
179
|
+
f"changed files: {len(plan.changed)}",
|
|
180
|
+
]
|
|
181
|
+
for path in plan.changed[:10]:
|
|
182
|
+
lines.append(f" {path}")
|
|
183
|
+
if len(plan.changed) > 10:
|
|
184
|
+
lines.append(f" ... and {len(plan.changed) - 10} more")
|
|
185
|
+
lines.append("")
|
|
186
|
+
for decision in plan.decisions:
|
|
187
|
+
mark = "RUN " if decision.decision == ci_impact.RUN else "skip"
|
|
188
|
+
lines.append(f" {mark} {decision.job}")
|
|
189
|
+
lines.append(f" {decision.reason}")
|
|
190
|
+
if plan.note:
|
|
191
|
+
lines.append(f"\n{plan.note}")
|
|
192
|
+
lines.append("\nRun it: c3_ci(action='run', mode='required')")
|
|
193
|
+
return finalize("c3_ci", args, "\n".join(lines),
|
|
194
|
+
f"{len(plan.selected)} required")
|
|
195
|
+
|
|
155
196
|
# ── run / rerun ──────────────────────────────────────────────────────
|
|
156
197
|
if action in ("run", "rerun"):
|
|
157
198
|
only = None
|
|
@@ -172,7 +213,9 @@ def handle_ci(action: str, job: str, run_id: str, allow_foreign: bool,
|
|
|
172
213
|
only=only, timeout=timeout or cr.DEFAULT_STEP_TIMEOUT,
|
|
173
214
|
workflow=workflow, event=event, engine=engine,
|
|
174
215
|
allow_side_effects=allow_side_effects,
|
|
175
|
-
network=network
|
|
216
|
+
network=network, mode=mode, base=base,
|
|
217
|
+
allow_host_mutation=allow_host_mutation,
|
|
218
|
+
no_cache=no_cache)
|
|
176
219
|
run = result.to_dict()
|
|
177
220
|
lines = [_verdict_line(run), f"run {run['run_id']} host={run['host_os']}"]
|
|
178
221
|
fp = run.get("fingerprint") or {}
|
|
@@ -245,6 +288,86 @@ def handle_ci(action: str, job: str, run_id: str, allow_foreign: bool,
|
|
|
245
288
|
f"{row.get('started_at', '')[:19]} {tally}")
|
|
246
289
|
return finalize("c3_ci", args, "\n".join(lines), f"{len(rows)} runs")
|
|
247
290
|
|
|
291
|
+
# ── history / intelligence ───────────────────────────────────────────
|
|
292
|
+
if action in ("history", "intel", "flaky"):
|
|
293
|
+
from services import ci_intel
|
|
294
|
+
data = ci_intel.analyse(project)
|
|
295
|
+
if not data["runs_analysed"]:
|
|
296
|
+
return finalize("c3_ci", args, data["note"], "no history")
|
|
297
|
+
lines = [data["note"], ""]
|
|
298
|
+
if data["flaky"]:
|
|
299
|
+
lines.append("FLAKY — passed AND failed on identical inputs:")
|
|
300
|
+
for job in data["flaky"]:
|
|
301
|
+
lines.append(f" {job['key']} {job['failed']}/{job['executed']} failed")
|
|
302
|
+
lines.append(" same fingerprint gave both answers, so the "
|
|
303
|
+
"code did not change between them")
|
|
304
|
+
lines.append("")
|
|
305
|
+
lines.append(f"{'job':<40}{'exec':>5}{'fail':>6}{'rate':>7}{'avg':>8}")
|
|
306
|
+
for job in data["jobs"][:15]:
|
|
307
|
+
rate = f"{job['fail_rate']:.0%}" if job["executed"] else "-"
|
|
308
|
+
avg = f"{job['avg_ms'] / 1000:.1f}s" if job["avg_ms"] else "-"
|
|
309
|
+
mark = "" if job["confident"] else " (low n)"
|
|
310
|
+
lines.append(f" {job['key']:<38}{job['executed']:>5}"
|
|
311
|
+
f"{job['failed']:>6}{rate:>7}{avg:>8}{mark}")
|
|
312
|
+
if data["slowest"]:
|
|
313
|
+
lines.append("\nSlowest: " + ", ".join(
|
|
314
|
+
f"{j['key']} {j['avg_ms'] / 1000:.1f}s" for j in data["slowest"][:3]))
|
|
315
|
+
return finalize("c3_ci", args, "\n".join(lines),
|
|
316
|
+
f"{len(data['flaky'])} flaky")
|
|
317
|
+
|
|
318
|
+
# ── publish (GitHub commit status) ───────────────────────────────────
|
|
319
|
+
if action == "publish":
|
|
320
|
+
from services import ci_github
|
|
321
|
+
run = cr.load_run(project, run_id)
|
|
322
|
+
if not run:
|
|
323
|
+
return finalize("c3_ci", args, "No run to publish.", "no runs")
|
|
324
|
+
res = ci_github.publish(project, run,
|
|
325
|
+
force=allow_side_effects,
|
|
326
|
+
dry_run=(job or "").lower() == "dry-run")
|
|
327
|
+
if res.get("dry_run"):
|
|
328
|
+
body = "\n".join([
|
|
329
|
+
f"Would POST {res['endpoint']}",
|
|
330
|
+
f" state={res['state']}",
|
|
331
|
+
f" {res['description']}",
|
|
332
|
+
])
|
|
333
|
+
return finalize("c3_ci", args, body, "dry run")
|
|
334
|
+
if not res.get("published"):
|
|
335
|
+
return finalize("c3_ci", args,
|
|
336
|
+
f"Not published: {res.get('reason')}", "refused")
|
|
337
|
+
body = (f"Published {res['state']} to {res['sha'][:8]} "
|
|
338
|
+
f"as {res['context']}.\n {res['description']}")
|
|
339
|
+
return finalize("c3_ci", args, body, res["state"])
|
|
340
|
+
|
|
341
|
+
# ── cache ────────────────────────────────────────────────────────────
|
|
342
|
+
if action == "cache":
|
|
343
|
+
from services import ci_cache
|
|
344
|
+
if (job or "").strip().lower() == "clear" or run_id == "clear":
|
|
345
|
+
freed = ci_cache.clear(project)
|
|
346
|
+
return finalize("c3_ci", args,
|
|
347
|
+
f"Cleared the CI cache ({freed / 1024:.0f} KB).",
|
|
348
|
+
"cache cleared")
|
|
349
|
+
info = ci_cache.stats(project)
|
|
350
|
+
rules = ci_impact.load_rules(project)
|
|
351
|
+
lines = [
|
|
352
|
+
f"Cached results: {info['results']}",
|
|
353
|
+
f"Dependency cache keys: {info['dependency_keys']}",
|
|
354
|
+
f"Location: {info['dir']}",
|
|
355
|
+
"",
|
|
356
|
+
"A job is reused when its definition, engine and inputs are all "
|
|
357
|
+
"unchanged since it last passed.",
|
|
358
|
+
]
|
|
359
|
+
if rules:
|
|
360
|
+
lines.append(
|
|
361
|
+
f"{len(rules)} job(s) declare inputs via ci.required_map, so "
|
|
362
|
+
"only those paths invalidate them.")
|
|
363
|
+
else:
|
|
364
|
+
lines.append(
|
|
365
|
+
"No ci.required_map: every job is fingerprinted over the whole "
|
|
366
|
+
"tree, so any edit invalidates every cached result. Declaring "
|
|
367
|
+
"inputs is what makes the cache bite.")
|
|
368
|
+
return finalize("c3_ci", args, "\n".join(lines),
|
|
369
|
+
f"{info['results']} cached")
|
|
370
|
+
|
|
248
371
|
# ── doctor ───────────────────────────────────────────────────────────
|
|
249
372
|
if action == "doctor":
|
|
250
373
|
from services import ci_act
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: code-context-control
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.85.0
|
|
4
4
|
Summary: Local MCP code-intelligence for AI coding tools: surgical search/read/edit, agent-config version history, path-level access + masking guards, and a multi-project hub.
|
|
5
5
|
Author-email: Dimitri Tselenchuk <dtselenc@gmail.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
cli/__init__.py,sha256=ec66drCZGNMRU4V6ov0zVhYZph1us12Vn8OvG_LJyRY,22
|
|
2
2
|
cli/_hook_utils.py,sha256=bx8246vdJQ0eCsmDSx2nrSw19t0MI1tde76SZr0uuCo,19168
|
|
3
|
-
cli/c3.py,sha256=
|
|
3
|
+
cli/c3.py,sha256=cdpKnoQymVSrAgNs9I9VoGb5mvonS3rbWEUVOhsW2hE,373267
|
|
4
4
|
cli/docs.html,sha256=X9M7qC7LWi8pPJNF-4yEM57jhuZ3WeTESECbdDOeNaA,143172
|
|
5
5
|
cli/edits.html,sha256=UjAhoCmBmQ89cklGvJqzC6eyNP2tc8H6T-e01DVkLvE,43418
|
|
6
6
|
cli/hook_access_guard.py,sha256=UGXZ6-3gVNxjVasAuCV9eUOBwqDHl3YnuDc3XVTpvL0,15942
|
|
@@ -21,7 +21,7 @@ cli/hook_terse_advisor.py,sha256=W_zaTfX2akgX-ifuUTsLu2H1atM8XmWt6jpPoeU7Pm0,625
|
|
|
21
21
|
cli/hub_server.py,sha256=Wqq6LenxvMLnJ93NX64Yx6UJcOFGyJngUq2IXoA-5Uw,145557
|
|
22
22
|
cli/hub_ui.html,sha256=nnGqWhWGCLfNisGUnoAlQFcBr6wPNsq-AEOR7uFtVkE,2942
|
|
23
23
|
cli/mcp_proxy.py,sha256=92htuT-p0j-cDTbyqlIJpGoQ85_Aw7UuB8L_Toi_u20,17511
|
|
24
|
-
cli/mcp_server.py,sha256=
|
|
24
|
+
cli/mcp_server.py,sha256=7XenAdF-gKbP4KlHEzTy8PpWpRhRTR_F_nKXxpNeZjU,52424
|
|
25
25
|
cli/progress.py,sha256=qTkNy0howOD3hXraMo4ok3dYTpBW5yG_tenKfF4UDM0,1470
|
|
26
26
|
cli/server.py,sha256=xJ0bGyY5V7hSyNl6wZycwJYt8ug5N_kbsxOihXvHlS0,165450
|
|
27
27
|
cli/ui.html,sha256=xcdt74nlFEXx-0Bx6-Okw-WSVZPAXL0iukxU0ytI6CA,5694
|
|
@@ -29,7 +29,7 @@ cli/ui_legacy.html,sha256=cI8tC6RKmE2NIJOcsu7CY-zT4VznjcbD6NTjxb_fvUY,378460
|
|
|
29
29
|
cli/ui_nano.html,sha256=UAwQ6bbTOXAoGq191AZ7slhngR9edJSa3IhqpynveDg,27740
|
|
30
30
|
cli/commands/__init__.py,sha256=0Z8MABNzwSFJGT4Xv9R5AJVR8XxraTsuVTz5b0bShmo,38
|
|
31
31
|
cli/commands/common.py,sha256=eh9x6r3E2k7qf_ZFFBbsBZ4wh5q7Z3bR4RWp3jcAk-Y,13340
|
|
32
|
-
cli/commands/parser.py,sha256=
|
|
32
|
+
cli/commands/parser.py,sha256=jhRYDP4-0BNOC1k_Jk_u_wzkKbBdMJ4IEWGQHdyFveo,54118
|
|
33
33
|
cli/guide/access.html,sha256=BelnEc4p7XgcH_ysTlMXL9ZHCD0O4CPLq3h5rvqPIh8,14790
|
|
34
34
|
cli/guide/bitbucket.html,sha256=G6xzhN0zFFfZJ3PuNoCiLkxQHl4xFayfHaZeN60WUhA,33606
|
|
35
35
|
cli/guide/credentials.html,sha256=ZRv5NwWFxPoI6rQH3VbGCKA67B2DHGUoyH-5eO2iKb8,36576
|
|
@@ -72,7 +72,7 @@ cli/tools/_helpers.py,sha256=Yo_ol8bFT2R5ayjT54pXLVZhW_PgpBVoM8I7pDoomCI,7823
|
|
|
72
72
|
cli/tools/agent.py,sha256=Fp9XLrOqjSP7_Ihq7VXjYXcGoXjw17-Hx3JMvoOVnhc,48105
|
|
73
73
|
cli/tools/artifacts.py,sha256=LkSdjRz1TuYSFD1H_GSsxvhXOogaMt_JMbOnmkLOSUU,7784
|
|
74
74
|
cli/tools/bitbucket.py,sha256=_hgORCW9-IF5xZJaznoONVfaTGKaX8VcU7ALrBeLhWo,27518
|
|
75
|
-
cli/tools/ci.py,sha256=
|
|
75
|
+
cli/tools/ci.py,sha256=fQ03RENywS5gqHjH26Fa4YiraBTvchlPcPSnVYwwKBs,19444
|
|
76
76
|
cli/tools/compress.py,sha256=IW2I2duAwj1Z2NPG7znX6rDcG3f83r5RvCVpR0yinmY,12881
|
|
77
77
|
cli/tools/credentials.py,sha256=nUsunY09zXYAX-D5oEPD6B2WaWicMLJDNjjxbdce4gE,10566
|
|
78
78
|
cli/tools/delegate.py,sha256=KXy4wZhex9HKixj9HVNIGKsHJHJwiPi6LAP3GRkZoxs,59469
|
|
@@ -114,7 +114,7 @@ cli/ui/components/sessions.js,sha256=FIKtil76B8tCkAmcFV7hlj6GQ_DCJK2jCzvEmdK7NBE
|
|
|
114
114
|
cli/ui/components/settings.js,sha256=ATbAjBlVIwCNpxq7s191b49a_INQV38iwmySqtJLYwY,79066
|
|
115
115
|
cli/ui/components/sidebar.js,sha256=K2ym2kUgpbyG-EBA_wBIIiqQY8qTatsiBZwzseMWuIQ,9939
|
|
116
116
|
cli/ui/components/tasks.js,sha256=vyKQ3uwoppMwvdEaHlhWXW4oWcAisx4NveqzMhsYqHo,38438
|
|
117
|
-
code_context_control-2.
|
|
117
|
+
code_context_control-2.85.0.dist-info/licenses/LICENSE,sha256=l8Kh5QCNWNvR6kIt8L0BUZvc2LAFiHv2c-FnsGnUZf4,11301
|
|
118
118
|
core/__init__.py,sha256=TSDCEcM4V7gcZVM3w2ykJaqEUch4Dkon-rivV17T73s,2501
|
|
119
119
|
core/config.py,sha256=YmkcZwedz_lfDM0ZuI_f4xpe98ixPLcSQFcTCHgRiRg,19206
|
|
120
120
|
core/ide.py,sha256=V6VVMVsFdmmcsMyxikjQp7z9xa42CWiHKS-ya-MAcG4,6172
|
|
@@ -177,11 +177,15 @@ services/auto_memory.py,sha256=LAQZtz5qbnn5FSw8hapO9kwbhqLpnQi70IWdNoNQ1V0,15258
|
|
|
177
177
|
services/benchmark_dashboard.py,sha256=iR-DnqnoKbqHMJ4d-ZkIvJBYfzwTa7r-jzO6j2BYDfQ,27711
|
|
178
178
|
services/bitbucket_client.py,sha256=JByovvtVZ6F4NcU611KVuTgKlT1rIsX2A2VlBDfvRV8,17509
|
|
179
179
|
services/bitbucket_credentials.py,sha256=2qLA9pQMol4y95y4DJMNBsBBPUsJQCKbLFo2iiCnfvI,7364
|
|
180
|
-
services/ci_act.py,sha256=
|
|
180
|
+
services/ci_act.py,sha256=fLcDsToaVbWmCDwWNOG7tgMWGvYKy09coa0HlBs1dak,13291
|
|
181
|
+
services/ci_cache.py,sha256=Too3XyeE-oeiqQm4IJ10giFvhIEr29YfDLUpZDl8SBU,13256
|
|
181
182
|
services/ci_expr.py,sha256=BsCUoL4ZN1Q8KjYJ07xWsPynV04Q8dg-ryIIKNOYk70,15641
|
|
182
183
|
services/ci_failures.py,sha256=32aJ_R5S-JE4fErhzs06ZQzhBVLawZ4brtXtdoJrnE0,8087
|
|
183
|
-
services/
|
|
184
|
-
services/
|
|
184
|
+
services/ci_github.py,sha256=2yXsVAiWMIvhxH7xsMJIO_3DxCvZbKJcSWt4zXyyWxE,6699
|
|
185
|
+
services/ci_impact.py,sha256=9_CJv2sNza0_qW7cSsMnxhJy_AGflDONjL9jKQeoWmc,10966
|
|
186
|
+
services/ci_intel.py,sha256=frr1jL-FXU_JNUoRW8TwnZo04fqUElAGGEsDEvgIeUs,7161
|
|
187
|
+
services/ci_runner.py,sha256=egkFGaOjAa45KIMFbGX7108R2wUQwUuLgJ4Iq7ygx9I,40768
|
|
188
|
+
services/ci_workflow.py,sha256=xfeUvzcm9GHiDdi7QAbg4SAvnzzuCCV3bmwdAMUTZKA,31133
|
|
185
189
|
services/circuit_breaker.py,sha256=ES6PpjL40gfDhtK88GeS6GFzsz5EOEEkZsB-DzX_tbw,3179
|
|
186
190
|
services/claude_md.py,sha256=WK2HsUxgYsDHFEWxxrA1GPyY9JLEkc_6vJkH_MQO2jU,51629
|
|
187
191
|
services/compressor.py,sha256=1K6LzOt7Qg3F50YJAFs2xky_jlrctDf2OqHQQoGuQGU,36022
|
|
@@ -272,8 +276,8 @@ tui/screens/search_view.py,sha256=MMHjVdlk3HZSuDBSvq8IGrqv_Mh5Us6YqXQ80bcWSMk,19
|
|
|
272
276
|
tui/screens/session_view.py,sha256=eZ1eDwHTvPOck1wCCviixtOaCxIkBT_95ytNNNriGNA,5991
|
|
273
277
|
tui/screens/stats.py,sha256=p81PjzdaIv7hllb8f45-rlVe4lJZwSdIMqu7e86_u5s,6223
|
|
274
278
|
tui/screens/ui_view.py,sha256=1QJCgLh2YfgWIpvzRG1KOGXYEaOYX6ojN61Azjf2oX0,2125
|
|
275
|
-
code_context_control-2.
|
|
276
|
-
code_context_control-2.
|
|
277
|
-
code_context_control-2.
|
|
278
|
-
code_context_control-2.
|
|
279
|
-
code_context_control-2.
|
|
279
|
+
code_context_control-2.85.0.dist-info/METADATA,sha256=kiLaxzH8a0HhMA8YE8uUjQF3Jxb1ARn6-cN6FlR9ofQ,25825
|
|
280
|
+
code_context_control-2.85.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
281
|
+
code_context_control-2.85.0.dist-info/entry_points.txt,sha256=7kX_WUsDCF2hbXzvbNyscyaBb9AeA-DJY5v_5hN0DlU,93
|
|
282
|
+
code_context_control-2.85.0.dist-info/top_level.txt,sha256=wRt41zBybVF3qAiNXHz9BURbkKvUvfhmWWtKMhaw6eE,29
|
|
283
|
+
code_context_control-2.85.0.dist-info/RECORD,,
|
services/ci_act.py
CHANGED
|
@@ -154,6 +154,42 @@ def side_effects(inst) -> list:
|
|
|
154
154
|
return found
|
|
155
155
|
|
|
156
156
|
|
|
157
|
+
# ── Host-mutation gate (native engine only) ─────────────────────────────────
|
|
158
|
+
# Learned the hard way, on this repository. A required-mode run selected every
|
|
159
|
+
# job and the native engine executed C3's own `test` job, whose steps are
|
|
160
|
+
# `python -m pip install -e ".[dev]"`. That uninstalled the copied C3 from
|
|
161
|
+
# site-packages and replaced it with an editable install pointing at the repo;
|
|
162
|
+
# the run then timed out mid-write and left the package — and every project's
|
|
163
|
+
# hooks — broken.
|
|
164
|
+
#
|
|
165
|
+
# The native engine has no isolation: a `run:` step executes as the user, on
|
|
166
|
+
# the user's machine, against the user's interpreter. Most CI jobs begin by
|
|
167
|
+
# installing dependencies, so "run the repo's real CI natively" means "let a
|
|
168
|
+
# YAML file reconfigure this machine". Under act the same step is contained and
|
|
169
|
+
# harmless, which is why the refusal points there rather than just saying no.
|
|
170
|
+
_HOST_MUTATION = re.compile(
|
|
171
|
+
r"(?:^|[\s;&|])(?:"
|
|
172
|
+
r"pip\s+(?:install|uninstall)|pip3\s+(?:install|uninstall)"
|
|
173
|
+
r"|python\s+-m\s+pip\s+(?:install|uninstall)"
|
|
174
|
+
r"|npm\s+(?:i|install)\s+(?:-g|--global)|npm\s+uninstall\s+(?:-g|--global)"
|
|
175
|
+
r"|yarn\s+global\s+add|pnpm\s+add\s+(?:-g|--global)"
|
|
176
|
+
r"|apt(?:-get)?\s+(?:install|remove|purge)|yum\s+install|dnf\s+install"
|
|
177
|
+
r"|brew\s+(?:install|uninstall)|choco\s+install|winget\s+install"
|
|
178
|
+
r"|cargo\s+install|go\s+install|gem\s+install"
|
|
179
|
+
r"|rustup\s|nvm\s+install|conda\s+(?:install|remove)"
|
|
180
|
+
r")\b", re.IGNORECASE)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def host_mutations(inst) -> list:
|
|
184
|
+
"""Steps that would reconfigure THIS machine if run without a container."""
|
|
185
|
+
found: list = []
|
|
186
|
+
for step in getattr(inst, "steps", []):
|
|
187
|
+
match = _HOST_MUTATION.search(str(getattr(step, "run", "") or ""))
|
|
188
|
+
if match:
|
|
189
|
+
found.append(f"step {step.index} runs `{match.group(0).strip()}`")
|
|
190
|
+
return found
|
|
191
|
+
|
|
192
|
+
|
|
157
193
|
# ── Command construction ────────────────────────────────────────────────────
|
|
158
194
|
|
|
159
195
|
def build_command(inst, project, act_path: str, event: str = "",
|