substratum-cli 0.2.8__tar.gz → 0.2.9__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.
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/PKG-INFO +1 -1
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/substratum_cli/__init__.py +1 -1
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/substratum_cli/_boot.py +2 -3
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/substratum_cli/commands/auth.py +8 -0
- substratum_cli-0.2.9/substratum_cli/commands/where.py +137 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/substratum_cli/render.py +22 -1
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/substratum_cli/session.py +104 -2
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/substratum_cli.egg-info/PKG-INFO +1 -1
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/substratum_cli.egg-info/SOURCES.txt +1 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/README.md +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/pyproject.toml +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/setup.cfg +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/substratum_cli/README.md +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/substratum_cli/__main__.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/substratum_cli/_tui.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/substratum_cli/_update.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/substratum_cli/_vendor/__init__.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/substratum_cli/_vendor/_pro_test_derive.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/substratum_cli/_vendor/client_gate.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/substratum_cli/_vendor/css_gate.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/substratum_cli/_wordmark.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/substratum_cli/account.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/substratum_cli/build_state.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/substratum_cli/cli.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/substratum_cli/codes.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/substratum_cli/commands/__init__.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/substratum_cli/commands/apply.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/substratum_cli/commands/commit.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/substratum_cli/commands/css_fix.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/substratum_cli/commands/explain.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/substratum_cli/commands/failing.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/substratum_cli/commands/fix.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/substratum_cli/commands/history.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/substratum_cli/commands/init_cmd.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/substratum_cli/commands/pr.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/substratum_cli/commands/recall.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/substratum_cli/commands/recheck.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/substratum_cli/commands/replay.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/substratum_cli/commands/rules.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/substratum_cli/commands/scaffold.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/substratum_cli/commands/show.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/substratum_cli/commands/undo.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/substratum_cli/commands/verify.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/substratum_cli/commands/write.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/substratum_cli/config.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/substratum_cli/diffs.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/substratum_cli/engine.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/substratum_cli/gitio.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/substratum_cli/identity.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/substratum_cli/langscan.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/substratum_cli/product_ops.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/substratum_cli/refusals.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/substratum_cli/remote.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/substratum_cli/result.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/substratum_cli/rulebook.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/substratum_cli/run_store.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/substratum_cli/runid.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/substratum_cli/style.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/substratum_cli.egg-info/dependency_links.txt +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/substratum_cli.egg-info/entry_points.txt +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/substratum_cli.egg-info/requires.txt +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/substratum_cli.egg-info/top_level.txt +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/tests/test_adaptive_prior.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/tests/test_alias_warrant_trace.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/tests/test_bidirectional_gate_contracts.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/tests/test_coherence_evolution.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/tests/test_coherences.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/tests/test_connection_web_api.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/tests/test_cross_entity_scoping.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/tests/test_data.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/tests/test_de28_negation_refuse.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/tests/test_definite_description_handler.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/tests/test_demo_regression_catalog.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/tests/test_demo_response_shape.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/tests/test_e1_e2_field_alignment.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/tests/test_extraction_class_write_time.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/tests/test_falsification_predictions.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/tests/test_gap_d_wh_normalization.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/tests/test_inference.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/tests/test_inflection_cvc_rule.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/tests/test_inflection_irregular.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/tests/test_intervention.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/tests/test_invariant.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/tests/test_legal_citation_handler.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/tests/test_lens_noop_seam.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/tests/test_markup_tag_handler.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/tests/test_nl_parser.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/tests/test_nl_parser_adversarial.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/tests/test_noise.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/tests/test_pass2_contextual.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/tests/test_perspectives.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/tests/test_polysemous_token_alias_guard.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/tests/test_possessive_normalization.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/tests/test_predicate_canon.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/tests/test_projection_gate.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/tests/test_projection_gate_tag_question.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/tests/test_query_normalizer.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/tests/test_records_answer_format.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/tests/test_referent_aboutness_gate.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/tests/test_referent_quality_gate.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/tests/test_refuse_message_topic_neutrality.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/tests/test_relation_detection.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/tests/test_relaxation.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/tests/test_residual_content_gate.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/tests/test_reverse_lookup_subject_filter.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/tests/test_sec_statement_aliases.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/tests/test_span_lens.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/tests/test_span_lens_query_integration.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/tests/test_substrate.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/tests/test_synonymy_detector.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/tests/test_table_ingest.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/tests/test_triple_ingest.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/tests/test_two_phase_schema.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/tests/test_user_asserted_aliases.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/tests/test_v2_api.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/tests/test_v2_api_claims.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/tests/test_v2_claim_determinism.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/tests/test_word_role.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/tests/test_word_role_wiring.py +0 -0
- {substratum_cli-0.2.8 → substratum_cli-0.2.9}/tests/test_xbrl_doc_aliases.py +0 -0
|
@@ -5,4 +5,4 @@ Import budget: this package is stdlib-only; the ONLY module reaching into backen
|
|
|
5
5
|
"""
|
|
6
6
|
# SINGLE SOURCE OF TRUTH for the release version. pyproject reads this (dynamic version), the boot shows
|
|
7
7
|
# it, and the update-check compares against it. To cut a release, bump ONLY this line, then publish.
|
|
8
|
-
__version__ = "0.2.
|
|
8
|
+
__version__ = "0.2.9"
|
|
@@ -86,9 +86,8 @@ def render(name, census, langs, substrate, substrate_note, substrate_ok, standin
|
|
|
86
86
|
|
|
87
87
|
# ---- WORDMARK + tagline ----
|
|
88
88
|
wm = " ".join("SUBSTRATUM")
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
+ c(CH, f" {dot} ") + c(LAB, "zero-LLM") + c(CH, f" {dot} ") + c(LAB, "deterministic"), ""]
|
|
89
|
+
out += ["", " " + c(L + B, wm) + " " + c(LAB, "Proof-Carrying Code Engine")
|
|
90
|
+
+ c(CH, f" {dot} ") + c(LAB, "Zero-LLM") + c(CH, f" {dot} ") + c(LAB, "Deterministic"), ""]
|
|
92
91
|
|
|
93
92
|
# ---- the CERTIFICATE ----
|
|
94
93
|
tl, tr, bl, br, h_, v_, ml, mr = ("+", "+", "+", "+", "-", "|", "+", "+") if ascii_mode \
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""`login` / `logout` / `whoami` -- connect the thin client to the hosted engine via GitHub."""
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
|
+
import os
|
|
4
5
|
import sys
|
|
5
6
|
|
|
6
7
|
from .. import account, remote
|
|
@@ -23,7 +24,14 @@ def cmd_login() -> int:
|
|
|
23
24
|
|
|
24
25
|
|
|
25
26
|
def cmd_logout() -> int:
|
|
27
|
+
if not account.is_logged_in():
|
|
28
|
+
sys.stdout.write(" not logged in -- nothing to disconnect.\n")
|
|
29
|
+
return 0
|
|
26
30
|
account.clear()
|
|
31
|
+
if os.environ.get("SUBSTRATUM_TOKEN"):
|
|
32
|
+
sys.stdout.write(" saved login cleared, but SUBSTRATUM_TOKEN is set in this shell and "
|
|
33
|
+
"still authenticates -- unset it to fully disconnect.\n")
|
|
34
|
+
return 0
|
|
27
35
|
sys.stdout.write(" logged out (local commands still work offline).\n")
|
|
28
36
|
return 0
|
|
29
37
|
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""`where <words>` -- ground plain-English domain terms against THIS repo: which files and lines
|
|
2
|
+
carry the feature the user named. The answer to "can we remove the spending check option from the
|
|
3
|
+
workflows section?" is never "not a command": it is WHERE that feature lives, plus the honest
|
|
4
|
+
ladder to a verified change (scaffold-test pins the behavior -> fix derives against it).
|
|
5
|
+
|
|
6
|
+
Deterministic and engine-free: term extraction is closed-class (stopwords out, identifier variants
|
|
7
|
+
generated -- snake/camel/pascal/kebab/joined), the search is `git grep` at the working tree, and
|
|
8
|
+
ranking is by variant specificity (a compound identifier hit outweighs a bare word). Results are a
|
|
9
|
+
first-class stored run.
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import os
|
|
14
|
+
import re
|
|
15
|
+
import subprocess
|
|
16
|
+
|
|
17
|
+
from .. import codes, run_store, runid
|
|
18
|
+
from .. import result as _result
|
|
19
|
+
|
|
20
|
+
_STOP = frozenset("""a an the can could we you i it is are was be do does did please help want
|
|
21
|
+
need from in on of to for and or with our my your this that these those app application website
|
|
22
|
+
site page section sections option options button tab menu part area remove delete disable enable
|
|
23
|
+
add create rename move change modify update refactor hide show make let lets get put set fix
|
|
24
|
+
""".split())
|
|
25
|
+
|
|
26
|
+
_MAX_FILES = 8
|
|
27
|
+
_MAX_HITS_PER_FILE = 3
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def extract_terms(text: str) -> list:
|
|
31
|
+
"""Content words, in order, stopwords and non-words dropped."""
|
|
32
|
+
words = re.findall(r"[A-Za-z][A-Za-z0-9_-]{2,}", (text or "").lower())
|
|
33
|
+
return [w for w in words if w not in _STOP]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def identifier_variants(terms: list) -> list:
|
|
37
|
+
"""The code shapes adjacent term pairs (and singles) take: spending+check ->
|
|
38
|
+
spending_check / spendingCheck / SpendingCheck / spending-check / spendingcheck.
|
|
39
|
+
Compounds first (highest specificity), then the singles."""
|
|
40
|
+
out, seen = [], set()
|
|
41
|
+
|
|
42
|
+
def add(v, weight):
|
|
43
|
+
if v.lower() not in seen:
|
|
44
|
+
seen.add(v.lower())
|
|
45
|
+
out.append((v, weight))
|
|
46
|
+
|
|
47
|
+
for a, b in zip(terms, terms[1:]):
|
|
48
|
+
add(f"{a}_{b}", 5)
|
|
49
|
+
add(f"{a}{b[:1].upper()}{b[1:]}", 5)
|
|
50
|
+
add(f"{a[:1].upper()}{a[1:]}{b[:1].upper()}{b[1:]}", 5)
|
|
51
|
+
add(f"{a}-{b}", 5)
|
|
52
|
+
add(f"{a} {b}", 4)
|
|
53
|
+
for t in terms:
|
|
54
|
+
add(t, 1)
|
|
55
|
+
return out
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _git_grep(repo: str, needle: str) -> list:
|
|
59
|
+
"""[(path, line_no, line_text)] for a fixed-string, case-insensitive working-tree grep."""
|
|
60
|
+
r = subprocess.run(["git", "grep", "-I", "-i", "-n", "-F", needle],
|
|
61
|
+
cwd=repo, capture_output=True, text=True, errors="replace", timeout=30)
|
|
62
|
+
hits = []
|
|
63
|
+
for ln in r.stdout.splitlines()[:400]:
|
|
64
|
+
parts = ln.split(":", 2)
|
|
65
|
+
if len(parts) == 3 and parts[1].isdigit():
|
|
66
|
+
if len(parts[2]) > 500: # a line that long is a data blob or minified, not code
|
|
67
|
+
continue
|
|
68
|
+
hits.append((parts[0], int(parts[1]), parts[2].strip()[:100]))
|
|
69
|
+
return hits
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def ground_terms(repo: str, text: str) -> dict:
|
|
73
|
+
"""The grounding: variants -> git grep -> files ranked by RARITY-WEIGHTED presence. A variant
|
|
74
|
+
hitting 3 files is a signal; one hitting 80 files ('check') is noise -- its weight is divided
|
|
75
|
+
by its spread. Presence counts once per (file, variant); sample lines come from the strongest
|
|
76
|
+
variant that hit the file, so the display shows the most specific evidence."""
|
|
77
|
+
terms = extract_terms(text)
|
|
78
|
+
variants = identifier_variants(terms)
|
|
79
|
+
per_variant: dict = {}
|
|
80
|
+
matched_variants: list = []
|
|
81
|
+
for needle, weight in variants:
|
|
82
|
+
hits = _git_grep(repo, needle)
|
|
83
|
+
if not hits:
|
|
84
|
+
continue
|
|
85
|
+
matched_variants.append(needle)
|
|
86
|
+
by_file: dict = {}
|
|
87
|
+
for path, line_no, line in hits:
|
|
88
|
+
by_file.setdefault(path, []).append({"line": line_no, "text": line, "term": needle})
|
|
89
|
+
per_variant[needle] = (weight, by_file)
|
|
90
|
+
file_score: dict = {}
|
|
91
|
+
file_best: dict = {} # path -> (effective weight of its strongest variant, samples)
|
|
92
|
+
for needle, (weight, by_file) in per_variant.items():
|
|
93
|
+
spread = len(by_file)
|
|
94
|
+
eff = weight if spread <= 3 else weight * 3.0 / spread # rarity: spread dilutes weight
|
|
95
|
+
for path, samples in by_file.items():
|
|
96
|
+
file_score[path] = file_score.get(path, 0.0) + eff
|
|
97
|
+
if path not in file_best or eff > file_best[path][0]:
|
|
98
|
+
file_best[path] = (eff, samples[:_MAX_HITS_PER_FILE])
|
|
99
|
+
ranked = sorted(file_score.items(), key=lambda kv: -kv[1])[:_MAX_FILES]
|
|
100
|
+
if ranked: # a file at <5% of the leader is a substring accident, not a place
|
|
101
|
+
floor = ranked[0][1] / 20.0
|
|
102
|
+
ranked = [(p, s) for p, s in ranked if s >= floor]
|
|
103
|
+
return {"terms": terms, "variants_matched": matched_variants,
|
|
104
|
+
"files": [{"path": p, "score": round(s, 2), "hits": file_best[p][1]}
|
|
105
|
+
for p, s in ranked]}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def next_steps(files: list) -> list:
|
|
109
|
+
"""The engineer's honest path from here. `open <n>` is the concrete move that works today: go to
|
|
110
|
+
the strongest match and make the change. The PROOF path is stated truthfully -- `verify` gates an
|
|
111
|
+
edit only against a test that pins the intended behavior (a bare verify on a hand edit reports,
|
|
112
|
+
it never proves), so the warrant step is named, not skipped. No `commit` here: commit follows a
|
|
113
|
+
PASS, and nothing is proven yet."""
|
|
114
|
+
if not files:
|
|
115
|
+
return []
|
|
116
|
+
top = files[0]
|
|
117
|
+
hits = top.get("hits") or []
|
|
118
|
+
name = top["path"].rsplit("/", 1)[-1]
|
|
119
|
+
loc = f"{name}:{hits[0]['line']}" if hits else name
|
|
120
|
+
return [
|
|
121
|
+
{"cmd": "open 1", "note": f"the strongest match ({loc}); open it and make the change"},
|
|
122
|
+
{"cmd": "verify --test <t>",
|
|
123
|
+
"note": "prove the edit against a test that pins the behavior (scaffold-test writes one)"},
|
|
124
|
+
]
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def cmd_where(repo: str, text: str) -> "_result.SubstratumResult":
|
|
128
|
+
repo = os.path.abspath(repo)
|
|
129
|
+
rid, key = runid.compute(repo, "where", {"text": " ".join(extract_terms(text))})
|
|
130
|
+
g = ground_terms(repo, text)
|
|
131
|
+
res = _result.make(codes.PASS, rid, "where",
|
|
132
|
+
data={"query": text, **g,
|
|
133
|
+
"next_steps": next_steps(g["files"]),
|
|
134
|
+
"ladder": ("scaffold-test pins the wanted behavior as a failing "
|
|
135
|
+
"test; fix then derives a verified change against it")})
|
|
136
|
+
run_store.store_run(repo, res, run_key=key)
|
|
137
|
+
return res
|
|
@@ -347,6 +347,27 @@ def render_human(res: SubstratumResult, stream=None, color: str | None = None) -
|
|
|
347
347
|
lab_col=(S.verdict_color(codes.FAIL) if st == "regressed" else None))
|
|
348
348
|
out.append(p(S.g("foot") + S.g("bar") * 2, vcol))
|
|
349
349
|
|
|
350
|
+
# ---- where: domain terms grounded in this repo ----
|
|
351
|
+
if res.operation == "where" and res.data:
|
|
352
|
+
d = res.data
|
|
353
|
+
files = d.get("files") or []
|
|
354
|
+
body("WHERE", f"{len(files)} place(s) carry {', '.join(d.get('variants_matched') or d.get('terms') or [])[:60]}"
|
|
355
|
+
if files else f"nothing in this repo matches {', '.join(d.get('terms') or [])[:60]}")
|
|
356
|
+
for i, f in enumerate(files, 1):
|
|
357
|
+
out.append(f"{rail} {p(str(i).rjust(2), S.CHROME)} {p(f['path'], S.PROVEN)}")
|
|
358
|
+
for h in f.get("hits", [])[:2]:
|
|
359
|
+
out.append(f"{rail} {p('L' + str(h['line']) + ' ' + h['text'][:68], S.CHROME)}")
|
|
360
|
+
out.append(p(S.g("foot") + S.g("bar") * 2, vcol))
|
|
361
|
+
if files:
|
|
362
|
+
steps = d.get("next_steps") or []
|
|
363
|
+
cw = max((len(s["cmd"]) for s in steps), default=8) # align notes to the widest command
|
|
364
|
+
for i, stp in enumerate(steps):
|
|
365
|
+
lead = p("next".ljust(6), S.LABEL) if i == 0 else " " * 6
|
|
366
|
+
out.append(f" {lead} {p(stp['cmd'].ljust(cw), S.GOLD)} {p(stp['note'], S.CHROME)}")
|
|
367
|
+
if not steps: # runs stored before next_steps existed
|
|
368
|
+
out.append(f" {p('next'.ljust(6), S.LABEL)} "
|
|
369
|
+
f"{p('scaffold-test pins the wanted behavior; fix derives against it', S.GOLD)}")
|
|
370
|
+
|
|
350
371
|
# ---- rules: standing preferences (intent made cumulative, recalled every build) ----
|
|
351
372
|
if res.operation == "rules" and res.data:
|
|
352
373
|
d = res.data
|
|
@@ -480,7 +501,7 @@ def render_human(res: SubstratumResult, stream=None, color: str | None = None) -
|
|
|
480
501
|
|
|
481
502
|
out.append(p(S.g("foot") + S.g("bar") * 2, vcol))
|
|
482
503
|
if not sm.get("full"): # the map is the summary; --full is the full reference
|
|
483
|
-
out.append(f" {p('
|
|
504
|
+
out.append(f" {p('next'.ljust(6), S.LABEL)} {p('summarize --full'.ljust(8), S.GOLD)}"
|
|
484
505
|
+ p(f" every one of {sm['functions']} functions {S.g('dot')} {sm['types']} types",
|
|
485
506
|
S.CHROME))
|
|
486
507
|
return "\n".join(out) + "\n"
|
|
@@ -38,7 +38,8 @@ _ALL_COMMANDS = set(_COMMANDS) | {"summary", "scaffold", "log", "hist", "tests",
|
|
|
38
38
|
"login", "logout", "whoami", "?", "h",
|
|
39
39
|
# session-state (the project-memory moat)
|
|
40
40
|
"why", "blame-decision", "blame", "recheck",
|
|
41
|
-
"remember", "forget", "rules", "sessions", "gc"
|
|
41
|
+
"remember", "forget", "rules", "sessions", "gc",
|
|
42
|
+
"where", "locate", "open"}
|
|
42
43
|
|
|
43
44
|
# commands whose argument is a run-id (for tab-completion + defaulting to the last run)
|
|
44
45
|
_RUNID_CMDS = {"apply", "undo", "commit", "show", "explain", "replay", "attest"}
|
|
@@ -63,6 +64,7 @@ class Session:
|
|
|
63
64
|
self.last_run: Optional[str] = None
|
|
64
65
|
self.last_applied: Optional[str] = None
|
|
65
66
|
self.last_op: Optional[str] = None
|
|
67
|
+
self._last_places: list = [] # files from the last `where`, for `open <n>`
|
|
66
68
|
# PERSISTENT recall: the build's query trail carries across sessions (!N / !! / up-arrow see the
|
|
67
69
|
# whole build, not just this process). Seeded from the ledger; appended to it on every query.
|
|
68
70
|
from . import build_state
|
|
@@ -249,7 +251,7 @@ class Session:
|
|
|
249
251
|
rail = ("\033[38;5;154m▌\033[0m" if (self.color and not ascii_mode) else "|")
|
|
250
252
|
brand = (f"\033[38;5;154m{S.g('brand')}\033[0m" if (self.color and not ascii_mode)
|
|
251
253
|
else pn(S.g("brand"), S.PROVEN))
|
|
252
|
-
self._say(brand + " " + pn("SUBSTRATUM", S.BOLD) + pn("
|
|
254
|
+
self._say(brand + " " + pn("SUBSTRATUM", S.BOLD) + pn(" Proof-Carrying · Deterministic", S.LABEL))
|
|
253
255
|
self._say(f" {rail} " + pn(name, S.BOLD) + " " + pn(census, S.LABEL))
|
|
254
256
|
self._say(f" {rail} " + pn(substrate, S.LABEL) + " " + state)
|
|
255
257
|
if st:
|
|
@@ -314,6 +316,9 @@ class Session:
|
|
|
314
316
|
# balanced quotes and never take this path.
|
|
315
317
|
parts = line.split()
|
|
316
318
|
cmd, args = parts[0], parts[1:]
|
|
319
|
+
# the pasted shell form ('substratum summarize --full') works verbatim in-session, flags kept
|
|
320
|
+
if cmd.lower() == "substratum" and args:
|
|
321
|
+
cmd, args = args[0], args[1:]
|
|
317
322
|
slashed = bool(cmd) and cmd[0] in "/:\\" # /exit, :q -- an explicit control command
|
|
318
323
|
if slashed:
|
|
319
324
|
cmd = cmd[1:]
|
|
@@ -364,6 +369,19 @@ class Session:
|
|
|
364
369
|
else:
|
|
365
370
|
self._say(" usage: why <file-or-symbol> (e.g. why auth.py)", _CYAN)
|
|
366
371
|
return None, True
|
|
372
|
+
# `open` takes a result number or a path -- a sentence ('open a pull request') is not a
|
|
373
|
+
# target; re-route it so prose beginning with 'open' never hits the editor door. A bare
|
|
374
|
+
# token that is a real file (Makefile, LICENSE) counts as a target even without a dot.
|
|
375
|
+
elif cmd == "open" and (not args or not (
|
|
376
|
+
args[0].isdigit()
|
|
377
|
+
or any(c in args[0] for c in "./_:\\")
|
|
378
|
+
or os.path.exists(os.path.join(self.repo, args[0].split(":", 1)[0])))):
|
|
379
|
+
rerouted = self._route_nl(line)
|
|
380
|
+
if rerouted is not None:
|
|
381
|
+
cmd, args = rerouted
|
|
382
|
+
else:
|
|
383
|
+
self._say(" usage: open <n> (a number from the last where) or open <file[:line]>", _CYAN)
|
|
384
|
+
return None, True
|
|
367
385
|
|
|
368
386
|
if cmd in ("help", "?", "h"):
|
|
369
387
|
self._help(" ".join(args))
|
|
@@ -463,6 +481,39 @@ class Session:
|
|
|
463
481
|
if cmd == "rules":
|
|
464
482
|
from .commands.rules import cmd_rules
|
|
465
483
|
return cmd_rules(self.repo), True
|
|
484
|
+
if cmd in ("where", "locate"):
|
|
485
|
+
# 'where did we leave off' is the catch-up idiom, not a grounding query
|
|
486
|
+
if cmd == "where" and re.search(r"^(did\s+|do\s+)?we\s+(left|leave)\s+off", " ".join(args).lower()):
|
|
487
|
+
from .commands.history import cmd_runs
|
|
488
|
+
return cmd_runs(self.repo), True
|
|
489
|
+
if not args:
|
|
490
|
+
self._say(' usage: where <what you are looking for> (e.g. where rate limiter)', _CYAN)
|
|
491
|
+
return None, True
|
|
492
|
+
from .commands.where import cmd_where
|
|
493
|
+
res = cmd_where(self.repo, " ".join(args))
|
|
494
|
+
if res is not None:
|
|
495
|
+
self._last_places = list((res.data or {}).get("files") or []) # `open <n>` targets
|
|
496
|
+
return res, True
|
|
497
|
+
if cmd == "open":
|
|
498
|
+
target = args[0]
|
|
499
|
+
if target.isdigit():
|
|
500
|
+
n = int(target)
|
|
501
|
+
if not self._last_places:
|
|
502
|
+
self._say(" nothing to open yet -- run where <words> first", _CYAN)
|
|
503
|
+
return None, True
|
|
504
|
+
if not 1 <= n <= len(self._last_places):
|
|
505
|
+
self._say(f" open takes 1..{len(self._last_places)} (from the last where)", _CYAN)
|
|
506
|
+
return None, True
|
|
507
|
+
place = self._last_places[n - 1]
|
|
508
|
+
hits = place.get("hits") or []
|
|
509
|
+
self._open_file(place["path"], hits[0]["line"] if hits else None)
|
|
510
|
+
return None, True
|
|
511
|
+
path, _, ln = target.rpartition(":")
|
|
512
|
+
if path and ln.isdigit():
|
|
513
|
+
self._open_file(path, int(ln))
|
|
514
|
+
else:
|
|
515
|
+
self._open_file(target, None)
|
|
516
|
+
return None, True
|
|
466
517
|
if cmd == "sessions":
|
|
467
518
|
from .commands.history import cmd_sessions
|
|
468
519
|
return cmd_sessions(self.repo), True
|
|
@@ -582,6 +633,10 @@ class Session:
|
|
|
582
633
|
if re.search(r"\b(summar\w+|tl;?dr)\b", low) or \
|
|
583
634
|
re.search(r"\b(describe|explain|overview|walk me through|what(?:'s| is| does| do| are)?)\b"
|
|
584
635
|
r".{0,40}\b(code|codebase|repo|repository|project|module|files?)\b", low):
|
|
636
|
+
# 'the FULL summary' / 'every function' carries through to --full, never silently dropped
|
|
637
|
+
if re.search(r"\bfull\b|\bevery\w*\b|\ball of it\b|\bin depth\b|\bdetailed?\b", low):
|
|
638
|
+
interp("summarize the codebase (full: every symbol)")
|
|
639
|
+
return ("summarize", ["--full"])
|
|
585
640
|
interp("summarize the codebase")
|
|
586
641
|
return ("summarize", [])
|
|
587
642
|
# verify current changes -- 'verify' is unambiguous; gate/check/review/validate need a change object
|
|
@@ -657,6 +712,15 @@ class Session:
|
|
|
657
712
|
if m:
|
|
658
713
|
interp(f"why {m.group(1)} (the decisions that shaped it)")
|
|
659
714
|
return ("why", [m.group(1)])
|
|
715
|
+
# a CHANGE REQUEST in domain language ('can we remove the spending check option from the
|
|
716
|
+
# workflows section?') -> ground the named terms in THIS repo + show the verified-change
|
|
717
|
+
# ladder. Never 'not a command' for legitimate engineering intent.
|
|
718
|
+
if re.search(r"\b(remove|delete|disable|enable|add|rename|move|change|modify|update|"
|
|
719
|
+
r"refactor|implement|hide|show)\b", low):
|
|
720
|
+
from .commands.where import extract_terms
|
|
721
|
+
if len(extract_terms(low)) >= 1:
|
|
722
|
+
interp("ground it in this repo (where does that live?)")
|
|
723
|
+
return ("where", line.split())
|
|
660
724
|
# a stated standing rule ('never use print statements here') -> suggest recording it, never
|
|
661
725
|
# auto-record (setting a rule is deliberate; see remember/rules)
|
|
662
726
|
if re.search(r"^(never|always|do not|don'?t)\b.*\b(use|using|write|import|call|commit|push|"
|
|
@@ -791,6 +855,40 @@ class Session:
|
|
|
791
855
|
return None # skipped: the ASK was already shown
|
|
792
856
|
return res
|
|
793
857
|
|
|
858
|
+
def _open_file(self, path: str, line_no=None) -> None:
|
|
859
|
+
"""Open a repo file in the user's editor, at the line when the editor can. Closed-class:
|
|
860
|
+
$VISUAL/$EDITOR first, then well-known GUI editors on PATH; the fallback is the path itself
|
|
861
|
+
(never an error -- the user came here to find a place, and the place is the answer)."""
|
|
862
|
+
import shutil
|
|
863
|
+
import subprocess
|
|
864
|
+
loc_note = f"{path}:{line_no}" if line_no else path
|
|
865
|
+
abspath = os.path.join(self.repo, path)
|
|
866
|
+
if not os.path.exists(abspath):
|
|
867
|
+
self._say(f" {path} is not in this repo (was it moved since that where?)", _CYAN)
|
|
868
|
+
return
|
|
869
|
+
raw = os.environ.get("VISUAL") or os.environ.get("EDITOR") or ""
|
|
870
|
+
editor = shlex.split(raw) if raw else []
|
|
871
|
+
if not editor:
|
|
872
|
+
found = next((c for c in ("code", "cursor", "code-insiders", "subl", "zed")
|
|
873
|
+
if shutil.which(c)), None)
|
|
874
|
+
editor = [found] if found else []
|
|
875
|
+
if not editor:
|
|
876
|
+
self._say(f" no editor found ($EDITOR is unset) -- it lives at {loc_note}", _CYAN)
|
|
877
|
+
return
|
|
878
|
+
base = os.path.basename(editor[0])
|
|
879
|
+
gui = {"code": "-g", "code-insiders": "-g", "cursor": "-g", "subl": "", "zed": ""}
|
|
880
|
+
try:
|
|
881
|
+
if base in gui: # GUI editors: path:line syntax, detached
|
|
882
|
+
loc = f"{abspath}:{line_no}" if line_no else abspath
|
|
883
|
+
cmd = editor + ([gui[base], loc] if gui[base] else [loc])
|
|
884
|
+
subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
885
|
+
self._say(f" opened {loc_note} in {base}", _DIM)
|
|
886
|
+
else: # terminal editors: +line, and hand over the tty
|
|
887
|
+
cmd = editor + ([f"+{line_no}"] if line_no else []) + [abspath]
|
|
888
|
+
subprocess.run(cmd, check=False)
|
|
889
|
+
except OSError as e:
|
|
890
|
+
self._say(f" could not start {base} ({e}) -- it lives at {loc_note}", _CYAN)
|
|
891
|
+
|
|
794
892
|
def _verify(self, args):
|
|
795
893
|
from .commands.verify import run_verify
|
|
796
894
|
# flags: --staged, --test <t> (repeatable). default = working-tree diff.
|
|
@@ -837,6 +935,8 @@ class Session:
|
|
|
837
935
|
("UNDERSTAND", "", [
|
|
838
936
|
("summarize", "describe this codebase (add --full for every symbol)"),
|
|
839
937
|
("failing", "run the tests and list what is failing"),
|
|
938
|
+
("where <words>", "find where something lives in this repo (grounds your words in code)"),
|
|
939
|
+
("open <n|file[:line]>", "open a found place in your editor (n from the last where)"),
|
|
840
940
|
]),
|
|
841
941
|
("BUILD", "verified or refused, never a guess", [
|
|
842
942
|
("fix <test>", "a verified fix -- a failing test, a css file, or a css intent (help fix)"),
|
|
@@ -863,6 +963,8 @@ class Session:
|
|
|
863
963
|
("doctor", "check the environment, harness, and connection"),
|
|
864
964
|
("init", "map what this repo can gate (the coverage contract)"),
|
|
865
965
|
("login", "connect the hosted engine for fix and write"),
|
|
966
|
+
("logout", "disconnect this machine (local commands keep working)"),
|
|
967
|
+
("whoami", "who is logged in, and to which server"),
|
|
866
968
|
]),
|
|
867
969
|
]
|
|
868
970
|
_HELP_ALSO = [
|
|
@@ -56,6 +56,7 @@ substratum_cli/commands/scaffold.py
|
|
|
56
56
|
substratum_cli/commands/show.py
|
|
57
57
|
substratum_cli/commands/undo.py
|
|
58
58
|
substratum_cli/commands/verify.py
|
|
59
|
+
substratum_cli/commands/where.py
|
|
59
60
|
substratum_cli/commands/write.py
|
|
60
61
|
tests/test_adaptive_prior.py
|
|
61
62
|
tests/test_alias_warrant_trace.py
|
|
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
|
|
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
|
|
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
|
|
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
|