@pmaddire/gcie 0.1.6 → 0.1.8
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/GCIE_USAGE.md +55 -0
- package/cli/commands/adaptation.py +169 -12
- package/package.json +1 -1
package/GCIE_USAGE.md
CHANGED
|
@@ -288,3 +288,58 @@ query -> scope -> profile/budget escalation -> targeted gap-fill -> rg fallback.
|
|
|
288
288
|
1. This file is intentionally generalized and adaptive for any repo.
|
|
289
289
|
2. Keep repo-specific tuning in learned overrides and `.gcie` state, not in global defaults.
|
|
290
290
|
3. If in doubt, choose the higher-accuracy path first, then optimize tokens after lock.
|
|
291
|
+
|
|
292
|
+
## Cross-Repo Adaptation Rules (Required)
|
|
293
|
+
|
|
294
|
+
Use these rules to keep adaptation portable across repositories.
|
|
295
|
+
|
|
296
|
+
1. Adaptation case source must be repo-local:
|
|
297
|
+
- Prefer generated cases from actual files in the target repo.
|
|
298
|
+
- Do not rely on hardcoded expected files from another codebase family.
|
|
299
|
+
- If report `case_source` is not repo-local, treat the run as invalid.
|
|
300
|
+
|
|
301
|
+
2. Accuracy lock is required, but selection must be cost-aware:
|
|
302
|
+
- First gate: `100%` must-have full-hit.
|
|
303
|
+
- If multiple candidates pass `100%`, choose the lowest `tokens_per_expected_hit`.
|
|
304
|
+
- Do not keep `slices` as active default when a `plain` candidate also has `100%` and is cheaper.
|
|
305
|
+
|
|
306
|
+
3. Near-miss rescue before expensive lock-in:
|
|
307
|
+
- If a cheaper candidate is below lock by one file/family (for example `90%`), run a short rescue cycle before accepting an expensive `100%` candidate:
|
|
308
|
+
1) targeted gap-fill for missing must-have file(s)
|
|
309
|
+
2) scope correction (subtree if clustered)
|
|
310
|
+
3) one budget rung increase
|
|
311
|
+
- Re-evaluate after rescue; prefer the cheaper candidate if it reaches `100%`.
|
|
312
|
+
|
|
313
|
+
4. Cost sanity guardrail:
|
|
314
|
+
- If selected active candidate is `>40%` more expensive than the cheapest candidate, mark status `accuracy_locked_but_cost_risky` and continue family-level refinement.
|
|
315
|
+
- Keep accuracy lock, but do not finalize global defaults until cost risk is reduced.
|
|
316
|
+
|
|
317
|
+
5. Family-scoped finalization:
|
|
318
|
+
- Finalize routing per family, not as one global winner.
|
|
319
|
+
- Example: keep `slices` only for families where it is uniquely required for `100%`; use `plain` on families where it is cheaper at equal hit rate.
|
|
320
|
+
|
|
321
|
+
6. Required report checks each run:
|
|
322
|
+
- `case_source`
|
|
323
|
+
- `full_hit_rate_pct`
|
|
324
|
+
- `tokens_per_query`
|
|
325
|
+
- `tokens_per_expected_hit`
|
|
326
|
+
- token delta between selected candidate and cheapest candidate
|
|
327
|
+
|
|
328
|
+
## Portable Validation Checklist (Any New Repo)
|
|
329
|
+
|
|
330
|
+
After running adaptation:
|
|
331
|
+
1. Confirm `status: ok`.
|
|
332
|
+
2. Confirm `case_source: generated_repo_local`.
|
|
333
|
+
3. Confirm `full_hit_rate_pct: 100` for selected final profile.
|
|
334
|
+
4. Compare selected profile vs cheapest candidate:
|
|
335
|
+
- if selected is much more expensive, run one rescue iteration.
|
|
336
|
+
5. Run a 50-query unique validation before trusting defaults broadly.
|
|
337
|
+
|
|
338
|
+
Commands:
|
|
339
|
+
```powershell
|
|
340
|
+
gcie.cmd adapt . --benchmark-size 10 --efficiency-iterations 5 --clear-profile
|
|
341
|
+
```
|
|
342
|
+
```powershell
|
|
343
|
+
gcie.cmd adapt . --benchmark-size 10 --efficiency-iterations 5
|
|
344
|
+
```
|
|
345
|
+
|
|
@@ -30,7 +30,28 @@ class CaseResult:
|
|
|
30
30
|
context_complete: bool
|
|
31
31
|
|
|
32
32
|
|
|
33
|
+
@dataclass(frozen=True, slots=True)
|
|
34
|
+
class AdaptCase:
|
|
35
|
+
name: str
|
|
36
|
+
query: str
|
|
37
|
+
intent: str
|
|
38
|
+
baseline_files: tuple[str, ...]
|
|
39
|
+
expected_files: tuple[str, ...]
|
|
40
|
+
|
|
41
|
+
|
|
33
42
|
_WORD_RE = re.compile(r"[A-Za-z0-9_./-]+")
|
|
43
|
+
_SOURCE_EXTS = {".py", ".js", ".jsx", ".ts", ".tsx", ".java", ".go", ".rs", ".cs", ".cpp", ".c", ".h"}
|
|
44
|
+
_IGNORED_DIRS = {
|
|
45
|
+
".git",
|
|
46
|
+
".gcie",
|
|
47
|
+
".planning",
|
|
48
|
+
".venv",
|
|
49
|
+
"node_modules",
|
|
50
|
+
"__pycache__",
|
|
51
|
+
"dist",
|
|
52
|
+
"build",
|
|
53
|
+
"coverage",
|
|
54
|
+
}
|
|
34
55
|
|
|
35
56
|
|
|
36
57
|
def _query_keywords(text: str) -> list[str]:
|
|
@@ -252,7 +273,121 @@ def _summarize(label: str, rows: list[CaseResult]) -> dict:
|
|
|
252
273
|
}
|
|
253
274
|
|
|
254
275
|
|
|
255
|
-
def
|
|
276
|
+
def _collect_source_files(repo_path: Path) -> list[str]:
|
|
277
|
+
files: list[str] = []
|
|
278
|
+
for path in repo_path.rglob("*"):
|
|
279
|
+
if not path.is_file():
|
|
280
|
+
continue
|
|
281
|
+
rel = path.relative_to(repo_path)
|
|
282
|
+
if any(part in _IGNORED_DIRS for part in rel.parts):
|
|
283
|
+
continue
|
|
284
|
+
if path.suffix.lower() not in _SOURCE_EXTS:
|
|
285
|
+
continue
|
|
286
|
+
files.append(rel.as_posix())
|
|
287
|
+
return sorted(files)
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def _static_cases_for_repo(repo_path: Path) -> list[AdaptCase]:
|
|
291
|
+
out: list[AdaptCase] = []
|
|
292
|
+
for case in list(BENCHMARK_CASES):
|
|
293
|
+
expected = tuple(case.expected_files)
|
|
294
|
+
if not expected:
|
|
295
|
+
continue
|
|
296
|
+
if not all((repo_path / rel).exists() for rel in expected):
|
|
297
|
+
continue
|
|
298
|
+
baseline = tuple(rel for rel in case.baseline_files if (repo_path / rel).exists())
|
|
299
|
+
if not baseline:
|
|
300
|
+
baseline = expected
|
|
301
|
+
out.append(
|
|
302
|
+
AdaptCase(
|
|
303
|
+
name=case.name,
|
|
304
|
+
query=case.query,
|
|
305
|
+
intent=case.intent,
|
|
306
|
+
baseline_files=baseline,
|
|
307
|
+
expected_files=expected,
|
|
308
|
+
)
|
|
309
|
+
)
|
|
310
|
+
return out
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def _generated_cases_for_repo(repo_path: Path, needed: int) -> list[AdaptCase]:
|
|
314
|
+
files = _collect_source_files(repo_path)
|
|
315
|
+
if not files:
|
|
316
|
+
return []
|
|
317
|
+
|
|
318
|
+
by_dir: dict[str, list[str]] = {}
|
|
319
|
+
for rel in files:
|
|
320
|
+
parent = str(Path(rel).parent).replace("\\", "/")
|
|
321
|
+
by_dir.setdefault(parent, []).append(rel)
|
|
322
|
+
|
|
323
|
+
rows: list[AdaptCase] = []
|
|
324
|
+
seen_names: set[str] = set()
|
|
325
|
+
|
|
326
|
+
def add_case(name: str, expected: tuple[str, ...], intent: str = "explore") -> None:
|
|
327
|
+
if len(rows) >= needed:
|
|
328
|
+
return
|
|
329
|
+
safe_name = re.sub(r"[^a-zA-Z0-9_]+", "_", name).strip("_").lower() or "case"
|
|
330
|
+
if safe_name in seen_names:
|
|
331
|
+
idx = 2
|
|
332
|
+
while f"{safe_name}_{idx}" in seen_names:
|
|
333
|
+
idx += 1
|
|
334
|
+
safe_name = f"{safe_name}_{idx}"
|
|
335
|
+
seen_names.add(safe_name)
|
|
336
|
+
symbols = []
|
|
337
|
+
for rel in expected:
|
|
338
|
+
stem = Path(rel).stem.lower()
|
|
339
|
+
symbols.extend([stem, "flow", "wiring"])
|
|
340
|
+
query = f"{' '.join(expected)} {' '.join(symbols[:6])}".strip()
|
|
341
|
+
rows.append(
|
|
342
|
+
AdaptCase(
|
|
343
|
+
name=safe_name,
|
|
344
|
+
query=query,
|
|
345
|
+
intent=intent,
|
|
346
|
+
baseline_files=expected,
|
|
347
|
+
expected_files=expected,
|
|
348
|
+
)
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
# Single-file probes.
|
|
352
|
+
for rel in files:
|
|
353
|
+
add_case(f"single_{Path(rel).stem}", (rel,), intent="explore")
|
|
354
|
+
if len(rows) >= max(needed // 2, 1):
|
|
355
|
+
break
|
|
356
|
+
|
|
357
|
+
# Same-directory pairs.
|
|
358
|
+
for parent, group in sorted(by_dir.items(), key=lambda item: item[0]):
|
|
359
|
+
if len(group) < 2:
|
|
360
|
+
continue
|
|
361
|
+
group = sorted(group)
|
|
362
|
+
for idx in range(len(group) - 1):
|
|
363
|
+
add_case(f"pair_{parent}_{idx}", (group[idx], group[idx + 1]), intent="explore")
|
|
364
|
+
if len(rows) >= needed:
|
|
365
|
+
return rows[:needed]
|
|
366
|
+
|
|
367
|
+
# Cross-directory pairs if still needed.
|
|
368
|
+
tops: dict[str, str] = {}
|
|
369
|
+
for rel in files:
|
|
370
|
+
top = Path(rel).parts[0] if Path(rel).parts else rel
|
|
371
|
+
tops.setdefault(top, rel)
|
|
372
|
+
top_files = list(tops.values())
|
|
373
|
+
for idx in range(len(top_files) - 1):
|
|
374
|
+
add_case(f"cross_{idx}", (top_files[idx], top_files[idx + 1]), intent="explore")
|
|
375
|
+
if len(rows) >= needed:
|
|
376
|
+
break
|
|
377
|
+
|
|
378
|
+
return rows[:needed]
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def _select_adaptation_cases(repo_path: Path, benchmark_size: int) -> tuple[list[AdaptCase], str]:
|
|
382
|
+
"""Select adaptation cases generated entirely from the target repo."""
|
|
383
|
+
benchmark_size = max(1, int(benchmark_size))
|
|
384
|
+
generated = _generated_cases_for_repo(repo_path, benchmark_size)
|
|
385
|
+
if generated:
|
|
386
|
+
return generated[:benchmark_size], "generated_repo_local"
|
|
387
|
+
return [], "none_available"
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def _write_back(repo_path: Path, best: dict, case_source: str, pipeline_status: str, cost_analysis: dict) -> None:
|
|
256
391
|
cfg_path = repo_path / ".gcie" / "context_config.json"
|
|
257
392
|
if cfg_path.exists():
|
|
258
393
|
try:
|
|
@@ -264,10 +399,12 @@ def _write_back(repo_path: Path, best: dict) -> None:
|
|
|
264
399
|
else:
|
|
265
400
|
cfg = {}
|
|
266
401
|
cfg["adaptation_pipeline"] = {
|
|
267
|
-
"status":
|
|
402
|
+
"status": pipeline_status,
|
|
268
403
|
"best_label": best.get("label"),
|
|
269
404
|
"full_hit_rate_pct": best.get("full_hit_rate_pct"),
|
|
270
405
|
"tokens_per_query": best.get("tokens_per_query"),
|
|
406
|
+
"case_source": case_source,
|
|
407
|
+
"cost_analysis": cost_analysis,
|
|
271
408
|
"updated_at": datetime.now(timezone.utc).isoformat(),
|
|
272
409
|
}
|
|
273
410
|
cfg_path.parent.mkdir(parents=True, exist_ok=True)
|
|
@@ -290,17 +427,15 @@ def run_post_init_adaptation(
|
|
|
290
427
|
|
|
291
428
|
clear_adaptive_profile(repo_path.as_posix())
|
|
292
429
|
|
|
293
|
-
cases =
|
|
430
|
+
cases, case_source = _select_adaptation_cases(repo_path, benchmark_size)
|
|
294
431
|
if not cases:
|
|
295
432
|
return {
|
|
296
433
|
"status": "no_benchmark_cases",
|
|
297
434
|
"repo": repo_path.as_posix(),
|
|
298
|
-
"
|
|
435
|
+
"case_source": case_source,
|
|
436
|
+
"message": "No repo-usable adaptation cases available.",
|
|
299
437
|
}
|
|
300
438
|
|
|
301
|
-
benchmark_size = max(1, min(len(cases), int(benchmark_size)))
|
|
302
|
-
cases = cases[:benchmark_size]
|
|
303
|
-
|
|
304
439
|
slices_rows = [_evaluate_slices_case(case) for case in cases]
|
|
305
440
|
plain_rows = [_evaluate_plain_case(case, allow_gapfill=False) for case in cases]
|
|
306
441
|
plain_gap_rows = [_evaluate_plain_case(case, allow_gapfill=True) for case in cases]
|
|
@@ -327,13 +462,37 @@ def run_post_init_adaptation(
|
|
|
327
462
|
if trial["full_hit_rate_pct"] >= active["full_hit_rate_pct"] and trial["tokens_per_query"] < active["tokens_per_query"]:
|
|
328
463
|
active = trial
|
|
329
464
|
|
|
330
|
-
|
|
465
|
+
cheapest = min(candidates, key=lambda item: (item["tokens_per_expected_hit"] or 10**9, item["tokens_per_query"]))
|
|
466
|
+
token_delta = int(active["total_tokens"] - cheapest["total_tokens"])
|
|
467
|
+
pct_delta = round((token_delta / max(1, int(cheapest["total_tokens"]))) * 100, 1)
|
|
468
|
+
|
|
469
|
+
pipeline_status = "ok"
|
|
470
|
+
if (
|
|
471
|
+
active.get("full_hit_rate_pct", 0.0) >= 100.0
|
|
472
|
+
and active.get("label") != cheapest.get("label")
|
|
473
|
+
and pct_delta > 40.0
|
|
474
|
+
):
|
|
475
|
+
pipeline_status = "accuracy_locked_but_cost_risky"
|
|
476
|
+
|
|
477
|
+
cost_analysis = {
|
|
478
|
+
"cheapest_label": cheapest.get("label"),
|
|
479
|
+
"selected_label": active.get("label"),
|
|
480
|
+
"selected_vs_cheapest_token_delta": token_delta,
|
|
481
|
+
"selected_vs_cheapest_pct_delta": pct_delta,
|
|
482
|
+
"risk_threshold_pct": 40.0,
|
|
483
|
+
"cost_risky": pipeline_status == "accuracy_locked_but_cost_risky",
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
_write_back(repo_path, active, case_source, pipeline_status, cost_analysis)
|
|
331
487
|
|
|
332
488
|
report = {
|
|
333
|
-
"status":
|
|
489
|
+
"status": pipeline_status,
|
|
334
490
|
"repo": repo_path.as_posix(),
|
|
335
|
-
"benchmark_size":
|
|
491
|
+
"benchmark_size": len(cases),
|
|
492
|
+
"requested_benchmark_size": int(benchmark_size),
|
|
336
493
|
"efficiency_iterations": int(efficiency_iterations),
|
|
494
|
+
"case_source": case_source,
|
|
495
|
+
"cost_analysis": cost_analysis,
|
|
337
496
|
"stages": {
|
|
338
497
|
"accuracy_candidates": [slices_summary, plain_summary, plain_gap_summary],
|
|
339
498
|
"selected_after_accuracy": best,
|
|
@@ -348,5 +507,3 @@ def run_post_init_adaptation(
|
|
|
348
507
|
out_path.write_text(json.dumps(report, indent=2), encoding="utf-8")
|
|
349
508
|
report["report_path"] = out_path.as_posix()
|
|
350
509
|
return report
|
|
351
|
-
|
|
352
|
-
|