seif-cli 0.5.0__tar.gz → 0.5.2__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.
- {seif_cli-0.5.0/src/seif_cli.egg-info → seif_cli-0.5.2}/PKG-INFO +1 -1
- {seif_cli-0.5.0 → seif_cli-0.5.2}/pyproject.toml +1 -1
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/analysis/stance_detector.py +51 -5
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/cli/cli.py +501 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/cli/wrapper.py +130 -2
- seif_cli-0.5.2/src/seif/context/host_init.py +319 -0
- seif_cli-0.5.2/src/seif/context/model_probe.py +319 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/context/registry.py +126 -0
- seif_cli-0.5.2/src/seif/plugins/claude-code/scripts/orchestra-probe.py +40 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2/src/seif_cli.egg-info}/PKG-INFO +1 -1
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif_cli.egg-info/SOURCES.txt +6 -0
- seif_cli-0.5.2/tests/test_audit_host.py +108 -0
- seif_cli-0.5.2/tests/test_host_init.py +163 -0
- seif_cli-0.5.2/tests/test_model_probe.py +147 -0
- seif_cli-0.5.2/tests/test_rebuild_registry.py +154 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/tests/test_stance_detector.py +49 -0
- seif_cli-0.5.0/src/seif/plugins/claude-code/scripts/orchestra-probe.py +0 -291
- {seif_cli-0.5.0 → seif_cli-0.5.2}/LICENSE +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/MANIFEST.in +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/README.md +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/setup.cfg +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/__init__.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/__main__.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/analysis/__init__.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/analysis/physical_constants.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/analysis/quality_gate.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/analysis/transcompiler.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/bridge/__init__.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/bridge/native_client.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/bridge/telegram_bot.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/cli/__init__.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/cli/__main__.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/cli/chat.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/cli/identity.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/cli/main.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/cli/resonance_display.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/cli/serve.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/cli/serve_v2.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/cli/setup.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/constants.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/context/__init__.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/context/advisor.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/context/code_compressor.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/context/context_bridge.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/context/context_importer.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/context/context_manager.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/context/context_qr.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/context/cycle.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/context/file_extractor.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/context/git_context.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/context/git_hooks.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/context/ingest.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/context/nucleus.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/context/ref.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/context/seif_io.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/context/sessions.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/context/workspace.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/core/__init__.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/core/fingerprint.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/core/resonance_encoding.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/core/resonance_gate.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/core/resonance_signal.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/core/signing.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/core/timestamping.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/core/transfer_function.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/core/triple_gate.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/data/RESONANCE.json +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/data/__init__.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/data/defaults/__init__.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/data/defaults/circuit-recovery-v1.seif +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/data/defaults/definitions-v1.seif +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/data/defaults/ise-dissonance-v1.seif +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/data/defaults/multi-agent-sync-v1.seif +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/data/defaults/onboarding.seif +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/data/defaults/partial-attention-axiom-v1.seif +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/data/defaults/seif-cycle-v1.seif +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/data/defaults/seif-os-architecture-v1.seif +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/data/defaults/triad-convergence-v1.seif +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/data/paths.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/plugins/__init__.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/plugins/claude-code/hooks/hooks.json +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/plugins/claude-code/scripts/circuit-check.sh +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/plugins/claude-code/scripts/circuit-monitor.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/plugins/claude-code/scripts/classification-gate.sh +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/plugins/claude-code/scripts/kernel-seed.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/plugins/claude-code/scripts/quality-gate.sh +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/plugins/claude-code/scripts/session-end.sh +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/plugins/claude-code/scripts/session-start.sh +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/plugins/claude-code/skills/gate/SKILL.md +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/plugins/claude-code/skills/status/SKILL.md +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/plugins/claude-code/skills/sync/SKILL.md +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/security/__init__.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif/security/mode.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif_cli.egg-info/dependency_links.txt +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif_cli.egg-info/entry_points.txt +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif_cli.egg-info/requires.txt +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/src/seif_cli.egg-info/top_level.txt +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/tests/test_advisor.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/tests/test_canonical_inputs.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/tests/test_code_compressor.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/tests/test_collaborative_seif.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/tests/test_context_qr.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/tests/test_context_repo.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/tests/test_git_context.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/tests/test_git_hooks.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/tests/test_init.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/tests/test_quality_gate.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/tests/test_ref.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/tests/test_registry.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/tests/test_resonance_gate.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/tests/test_seif_io.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/tests/test_transcompiler.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/tests/test_transfer_function.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/tests/test_triple_gate.py +0 -0
- {seif_cli-0.5.0 → seif_cli-0.5.2}/tests/test_workspace.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: seif-cli
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.2
|
|
4
4
|
Summary: Measure AI output quality, protect sensitive data, watch your AI environment resonate. Quality Gate, Classification, Sentinel & Auto-Healing, SEIF OS.
|
|
5
5
|
Author: André Cunha Antero de Carvalho
|
|
6
6
|
License: CC-BY-NC-SA-4.0
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "seif-cli"
|
|
7
|
-
version = "0.5.
|
|
7
|
+
version = "0.5.2"
|
|
8
8
|
description = "Measure AI output quality, protect sensitive data, watch your AI environment resonate. Quality Gate, Classification, Sentinel & Auto-Healing, SEIF OS."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "CC-BY-NC-SA-4.0"}
|
|
@@ -55,8 +55,15 @@ class StanceAnalysis:
|
|
|
55
55
|
return "\n".join(lines)
|
|
56
56
|
|
|
57
57
|
|
|
58
|
-
# Patterns that indicate verifiable content
|
|
58
|
+
# Patterns that indicate verifiable content.
|
|
59
|
+
# Two groups: (1) original physics/EE/math patterns, (2) software-engineering
|
|
60
|
+
# verifiable artifacts added by the s14 calibration debate (semver, SHAs,
|
|
61
|
+
# file:line refs, test counts, ISO timestamps, PR refs, code identifiers, etc.).
|
|
62
|
+
# The split exists because session summaries are dense in (2) and were scoring
|
|
63
|
+
# F as "low data" before. Calibrated against 27 positive + 14 negative samples
|
|
64
|
+
# at 100% recall / 0% false positives.
|
|
59
65
|
VERIFIABLE_PATTERNS = [
|
|
66
|
+
# --- Group 1: physics / EE / math ---
|
|
60
67
|
r'\d+\.?\d*\s*(%|Hz|Ω|ohm|mH|μF|uF|nF|pF|dB|°|deg|rad|ms|kHz|MHz|V|A|W|bpm|rpm)',
|
|
61
68
|
r'[=≈≠<>]\s*\d',
|
|
62
69
|
r'[ζφωπ√]',
|
|
@@ -69,9 +76,41 @@ VERIFIABLE_PATTERNS = [
|
|
|
69
76
|
r'\b(?:theorem|proof|exhaustive|brute.?force)\b',
|
|
70
77
|
r'(?:ISE|IAE|ITAE|RLC|PCB|BOM|DRC)',
|
|
71
78
|
r'formal.?symbolic',
|
|
72
|
-
r'\b\w+[_]\w+\s*=',
|
|
73
|
-
r'\b[A-Z]\([a-z]\)',
|
|
74
|
-
r'property|unique|halving',
|
|
79
|
+
r'\b\w+[_]\w+\s*=', # variable_name = ...
|
|
80
|
+
r'\b[A-Z]\([a-z]\)', # H(s), F(x)
|
|
81
|
+
r'property|unique|halving', # math property language
|
|
82
|
+
|
|
83
|
+
# --- Group 2: software-engineering verifiable artifacts ---
|
|
84
|
+
r'\bv?\d+\.\d+\.\d+(?:[-+][\w.]+)?\b', # semver
|
|
85
|
+
r'\b(?:commit|tag|branch|sha|hash|merge|revert|cherry-?pick|HEAD)\s+[0-9a-f]{7,12}\b', # short SHA in context
|
|
86
|
+
r'\b[0-9a-f]{40}\b', # long SHA
|
|
87
|
+
r'\b(?:sha\d{2,3}|md5|blake[23]b?):[0-9a-f]{16,128}\b', # prefixed digest
|
|
88
|
+
r'\b[\w./-]+\.(?:py|ts|tsx|js|jsx|go|rs|java|cpp|c|h|hpp|md|json|yaml|yml|toml|sh|rb|php|cs|kt|swift|sql|html|css|scss):\d+(?:-\d+)?\b', # file:line
|
|
89
|
+
r'(?:#|\b)L\d+(?:-L?\d+)?\b', # GitHub line anchors
|
|
90
|
+
r'\b\d+\s+(?:tests?|specs?|suites?|cases?|assertions?|examples?)\b\s*(?:passing|failing|skipped|pending|run|across|in)?', # test count fwd
|
|
91
|
+
r'\b(?:tests?|specs?|suites?|cases?|assertions?|examples?)\s+(?:passing|failing|skipped|pending)?\s*(?:is|are|were|equals?|=)\s+\d+\b', # test count inv
|
|
92
|
+
r'\b(?:added|removed|deleted|modified|fixed|merged|reverted|created|closed|reopened|broke|patched|migrated)\s+\d+\b', # eng verb + count
|
|
93
|
+
r'\b\d{4}-\d{2}-\d{2}(?:[T ]\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:?\d{2})?)?\b', # ISO 8601
|
|
94
|
+
r'(?<!\w)(?:#\d+|(?:PR|GH|MR|RFC|RFD|ISSUE|JIRA|[A-Z]{2,8})-\d+)\b', # PR / issue refs
|
|
95
|
+
r'\b(?:returns?|status|code|HTTP|got|expected|received|responded\s+with)\s+(?:1\d{2}|2\d{2}|3\d{2}|4\d{2}|5\d{2})\b', # HTTP verb-prefixed
|
|
96
|
+
r'\b(?:1\d{2}|2\d{2}|3\d{2}|4\d{2}|5\d{2})\s+(?:OK|Created|Accepted|No\s+Content|Moved|Found|Bad\s+Request|Unauthorized|Forbidden|Not\s+Found|Conflict|Gone|Too\s+Many|Internal\s+Server|Bad\s+Gateway|Service\s+Unavailable|Gateway\s+Timeout)\b', # HTTP code + reason
|
|
97
|
+
r'(?:^|[\s(\[])(?:localhost|127\.0\.0\.1|0\.0\.0\.0|::1)?:(?:[1-9]\d{0,4})\b', # port number
|
|
98
|
+
r'\b[\w.-]+@\^?\d+\.\d+(?:\.\d+)?(?:[-+][\w.]+)?\b', # package@version
|
|
99
|
+
r'\b[a-z][a-z0-9_]+\([^)]{0,80}\)', # snake_case(...)
|
|
100
|
+
r'\b[A-Z][a-zA-Z0-9]+\([^)]{0,80}\)', # PascalCase(...)
|
|
101
|
+
r'\b\w+\.\w+\([^)]{0,80}\)', # foo.bar(...)
|
|
102
|
+
r'[+\-−]\d+\s*(?:[/,]\s*[+\-−]\d+|lines?|loc)?', # diff stats
|
|
103
|
+
r'\bexit(?:ed|s)?\s+(?:with\s+)?(?:code\s+)?-?\d+\b', # exit code
|
|
104
|
+
r'\breturn\s+code\s+-?\d+\b', # return code
|
|
105
|
+
r'\b\d+(?:\.\d+)?\s*(?:[KMGTP]i?B|bytes?|kbps|Mbps|Gbps)\b', # size with unit
|
|
106
|
+
r'\b\d+(?:\.\d+)?\s*(?:ns|us|μs|ms|min|sec|hr|hrs|hours?|minutes?|seconds?)\b', # duration
|
|
107
|
+
r'\b\d+m\s*\d+s\b', # 4m 32s
|
|
108
|
+
r'\b\d+h\s*\d+m\b', # 1h 18m
|
|
109
|
+
r'\b(?:feature|bugfix|hotfix|release|chore|refactor|fix|feat)/[\w./-]+\b', # branch convention
|
|
110
|
+
# File paths — require leading slash, ./, ../, OR dotted extension
|
|
111
|
+
# (rejects casual prose like "production/staging environments").
|
|
112
|
+
r'(?:^|(?<=[\s(\[]))(?:\.{1,2}/|/)[\w-]+(?:/[\w.-]+)+',
|
|
113
|
+
r'\b[\w-]+(?:/[\w.-]+)+\.[a-z]{1,6}\b',
|
|
75
114
|
]
|
|
76
115
|
|
|
77
116
|
# Patterns that indicate metaphorical/interpretive drift
|
|
@@ -98,7 +137,14 @@ def analyze(text: str) -> StanceAnalysis:
|
|
|
98
137
|
This does NOT judge the truth of claims. It measures the RATIO of
|
|
99
138
|
verifiable to interpretive content, enabling informed reading.
|
|
100
139
|
"""
|
|
101
|
-
|
|
140
|
+
# Sentence boundary rules:
|
|
141
|
+
# - Split on `.!?` only when NOT inside a decimal (no digit immediately
|
|
142
|
+
# after the punctuation), OR when followed by whitespace + a capital
|
|
143
|
+
# letter (the "0.612. This" case — punctuation preceded by a digit
|
|
144
|
+
# but clearly closing a sentence).
|
|
145
|
+
# - Newlines always split.
|
|
146
|
+
splitter = re.compile(r'[.!?](?!\d)(?=\s+[A-Z]|\s*$|\s+\Z)|(?<![0-9])[.!?]|\n')
|
|
147
|
+
sentences = [s.strip() for s in splitter.split(text) if len(s.strip()) > 10]
|
|
102
148
|
|
|
103
149
|
if len(sentences) < 2:
|
|
104
150
|
return StanceAnalysis(
|
|
@@ -3305,6 +3305,432 @@ def cmd_list():
|
|
|
3305
3305
|
print(f" ... and {len(unregistered) - 3} more")
|
|
3306
3306
|
|
|
3307
3307
|
|
|
3308
|
+
def cmd_audit_host(fix: bool = False, dry_run: bool = False):
|
|
3309
|
+
"""Audit ~/.seif/ host-level structure across the 5 architecture layers.
|
|
3310
|
+
|
|
3311
|
+
Layer A — Protocol (RESONANCE.json deployed)
|
|
3312
|
+
Layer B — Init-generated (config.json, registry.json with current workspace)
|
|
3313
|
+
Layer C — Derived (mapper.json, nucleus.seif, model_registry.json freshness)
|
|
3314
|
+
Layer D — Per-host config (machine_id present)
|
|
3315
|
+
Layer E — Heartbeat liveness
|
|
3316
|
+
|
|
3317
|
+
With --fix, calls the canonical regenerator for each missing/stale item.
|
|
3318
|
+
"""
|
|
3319
|
+
import json as _json
|
|
3320
|
+
import time as _time
|
|
3321
|
+
from pathlib import Path as _P
|
|
3322
|
+
from datetime import datetime, timezone
|
|
3323
|
+
|
|
3324
|
+
try:
|
|
3325
|
+
from seif.data.paths import get_user_home, get_resonance_path
|
|
3326
|
+
from seif.context.registry import (
|
|
3327
|
+
load_registry, find_context_entry, rebuild_registry,
|
|
3328
|
+
)
|
|
3329
|
+
from seif.context.host_init import load_machine_id, ensure_machine_id
|
|
3330
|
+
from seif.context.model_probe import (
|
|
3331
|
+
probe_all, write_registry as write_model_registry,
|
|
3332
|
+
get_registry_path as get_model_registry_path,
|
|
3333
|
+
)
|
|
3334
|
+
except ImportError as e:
|
|
3335
|
+
print(f"Installation error: {e}")
|
|
3336
|
+
return
|
|
3337
|
+
|
|
3338
|
+
home = get_user_home()
|
|
3339
|
+
cwd = _P.cwd()
|
|
3340
|
+
findings = [] # (layer, status, message, fixer)
|
|
3341
|
+
|
|
3342
|
+
# ── Layer A: Protocol artefacts ─────────────────────────────────────────
|
|
3343
|
+
resonance_canonical = get_resonance_path()
|
|
3344
|
+
resonance_deployed = home / "RESONANCE.json"
|
|
3345
|
+
if not resonance_canonical.exists():
|
|
3346
|
+
findings.append(("A", "FAIL", "RESONANCE.json missing in seif package — reinstall seif-cli", None))
|
|
3347
|
+
elif not resonance_deployed.exists():
|
|
3348
|
+
def _fix_resonance():
|
|
3349
|
+
import shutil as _sh
|
|
3350
|
+
_sh.copy2(resonance_canonical, resonance_deployed)
|
|
3351
|
+
return f"deployed {resonance_canonical} → {resonance_deployed}"
|
|
3352
|
+
findings.append(("A", "MISSING", "~/.seif/RESONANCE.json not deployed", _fix_resonance))
|
|
3353
|
+
else:
|
|
3354
|
+
findings.append(("A", "OK", f"RESONANCE.json deployed ({resonance_deployed.stat().st_size}B)", None))
|
|
3355
|
+
|
|
3356
|
+
# ── Layer B: Init-generated ─────────────────────────────────────────────
|
|
3357
|
+
workspace_seif = cwd / ".seif"
|
|
3358
|
+
in_workspace = workspace_seif.is_dir()
|
|
3359
|
+
|
|
3360
|
+
config_path = home / "config.json"
|
|
3361
|
+
if not config_path.exists():
|
|
3362
|
+
findings.append(("B", "MISSING", "~/.seif/config.json absent — run seif --init in a workspace", None))
|
|
3363
|
+
else:
|
|
3364
|
+
findings.append(("B", "OK", f"config.json present ({config_path.stat().st_size}B)", None))
|
|
3365
|
+
|
|
3366
|
+
registry_path = home / "registry.json"
|
|
3367
|
+
if not registry_path.exists():
|
|
3368
|
+
findings.append(("B", "MISSING", "~/.seif/registry.json absent",
|
|
3369
|
+
lambda: rebuild_registry([str(cwd.parent)]) and "rebuilt"))
|
|
3370
|
+
else:
|
|
3371
|
+
registry = load_registry()
|
|
3372
|
+
if in_workspace:
|
|
3373
|
+
entry = find_context_entry(registry, str(workspace_seif))
|
|
3374
|
+
if not entry:
|
|
3375
|
+
def _fix_registry():
|
|
3376
|
+
rebuild_registry([str(cwd.parent), str(cwd)])
|
|
3377
|
+
return "rebuilt with current workspace"
|
|
3378
|
+
findings.append(("B", "STALE",
|
|
3379
|
+
f"current workspace {workspace_seif} not in registry "
|
|
3380
|
+
f"({len(registry.get('contexts', []))} entries)",
|
|
3381
|
+
_fix_registry))
|
|
3382
|
+
else:
|
|
3383
|
+
findings.append(("B", "OK",
|
|
3384
|
+
f"registry has {len(registry.get('contexts', []))} entries, current workspace registered",
|
|
3385
|
+
None))
|
|
3386
|
+
else:
|
|
3387
|
+
findings.append(("B", "OK",
|
|
3388
|
+
f"registry has {len(registry.get('contexts', []))} entries",
|
|
3389
|
+
None))
|
|
3390
|
+
|
|
3391
|
+
# ── Layer C: Derived (workspace-scoped) ─────────────────────────────────
|
|
3392
|
+
if in_workspace:
|
|
3393
|
+
for fname, label in [("mapper.json", "module index"),
|
|
3394
|
+
("nucleus.seif", "workspace context")]:
|
|
3395
|
+
f = workspace_seif / fname
|
|
3396
|
+
if not f.exists():
|
|
3397
|
+
findings.append(("C", "MISSING",
|
|
3398
|
+
f".seif/{fname} absent — run seif --sync to regenerate",
|
|
3399
|
+
None))
|
|
3400
|
+
else:
|
|
3401
|
+
age_h = (_time.time() - f.stat().st_mtime) / 3600
|
|
3402
|
+
state = "OK" if age_h < 24 * 7 else "STALE"
|
|
3403
|
+
findings.append(("C", state,
|
|
3404
|
+
f".seif/{fname} ({label}, age {age_h:.1f}h)",
|
|
3405
|
+
None))
|
|
3406
|
+
|
|
3407
|
+
# model_registry.json freshness (host-level)
|
|
3408
|
+
mrp = get_model_registry_path()
|
|
3409
|
+
if not mrp.exists():
|
|
3410
|
+
def _fix_models():
|
|
3411
|
+
r = probe_all()
|
|
3412
|
+
write_model_registry(r)
|
|
3413
|
+
return f"probed {len(r.models)} models"
|
|
3414
|
+
findings.append(("C", "MISSING", "~/.seif/model_registry.json absent — run seif --probe-models", _fix_models))
|
|
3415
|
+
else:
|
|
3416
|
+
age_h = (_time.time() - mrp.stat().st_mtime) / 3600
|
|
3417
|
+
if age_h > 24:
|
|
3418
|
+
def _fix_models_stale():
|
|
3419
|
+
r = probe_all()
|
|
3420
|
+
write_model_registry(r)
|
|
3421
|
+
return f"reprobed {len(r.models)} models"
|
|
3422
|
+
findings.append(("C", "STALE",
|
|
3423
|
+
f"model_registry.json age {age_h:.1f}h > 24h",
|
|
3424
|
+
_fix_models_stale))
|
|
3425
|
+
else:
|
|
3426
|
+
findings.append(("C", "OK", f"model_registry.json fresh (age {age_h:.1f}h)", None))
|
|
3427
|
+
|
|
3428
|
+
# ── Layer D: Per-host ───────────────────────────────────────────────────
|
|
3429
|
+
mid = load_machine_id()
|
|
3430
|
+
if mid is None:
|
|
3431
|
+
def _fix_machine_id():
|
|
3432
|
+
payload, _ = ensure_machine_id()
|
|
3433
|
+
return f"created with label={payload['label']}"
|
|
3434
|
+
findings.append(("D", "MISSING", "~/.seif/machine_id absent — run seif --init-host", _fix_machine_id))
|
|
3435
|
+
else:
|
|
3436
|
+
findings.append(("D", "OK",
|
|
3437
|
+
f"machine_id {mid.get('label','?')}/{mid.get('role','?')} "
|
|
3438
|
+
f"fp={mid.get('fingerprint','?')[:8]}",
|
|
3439
|
+
None))
|
|
3440
|
+
|
|
3441
|
+
# ── Layer E: Heartbeat liveness ─────────────────────────────────────────
|
|
3442
|
+
hb_file = home / "heartbeat"
|
|
3443
|
+
if not hb_file.exists():
|
|
3444
|
+
findings.append(("E", "MISSING",
|
|
3445
|
+
"~/.seif/heartbeat not present — start heartbeatd "
|
|
3446
|
+
"(seif-resonance-bridge/seif-heartbeatd.py --daemon)",
|
|
3447
|
+
None))
|
|
3448
|
+
else:
|
|
3449
|
+
try:
|
|
3450
|
+
data = _json.loads(hb_file.read_text())
|
|
3451
|
+
ts = data.get("timestamp", "")
|
|
3452
|
+
from datetime import datetime as _dt
|
|
3453
|
+
t = _dt.fromisoformat(ts.replace("Z", "+00:00"))
|
|
3454
|
+
age_s = (_dt.now(timezone.utc) - t).total_seconds()
|
|
3455
|
+
if age_s > 300:
|
|
3456
|
+
findings.append(("E", "STALE",
|
|
3457
|
+
f"heartbeat {age_s:.0f}s old (>5min) — heartbeatd may be down",
|
|
3458
|
+
None))
|
|
3459
|
+
else:
|
|
3460
|
+
findings.append(("E", "OK",
|
|
3461
|
+
f"heartbeat {age_s:.0f}s old, machine={data.get('machine','?')}",
|
|
3462
|
+
None))
|
|
3463
|
+
except Exception as e:
|
|
3464
|
+
findings.append(("E", "WARN", f"heartbeat unparseable: {e}", None))
|
|
3465
|
+
|
|
3466
|
+
# ── Report ──────────────────────────────────────────────────────────────
|
|
3467
|
+
print(f"SEIF audit — host={home.parent.name}, cwd={cwd}")
|
|
3468
|
+
print()
|
|
3469
|
+
icons = {"OK": "✓", "MISSING": "✗", "STALE": "~", "FAIL": "!", "WARN": "?"}
|
|
3470
|
+
layer_labels = {
|
|
3471
|
+
"A": "Protocol ",
|
|
3472
|
+
"B": "Init-generated",
|
|
3473
|
+
"C": "Derived ",
|
|
3474
|
+
"D": "Per-host ",
|
|
3475
|
+
"E": "Heartbeat ",
|
|
3476
|
+
}
|
|
3477
|
+
counts = {"OK": 0, "MISSING": 0, "STALE": 0, "FAIL": 0, "WARN": 0}
|
|
3478
|
+
fixable = []
|
|
3479
|
+
for layer, status, msg, fixer in findings:
|
|
3480
|
+
icon = icons.get(status, "?")
|
|
3481
|
+
print(f" [{icon}] {layer_labels[layer]} [{status:<7}] {msg}")
|
|
3482
|
+
counts[status] += 1
|
|
3483
|
+
if fixer is not None:
|
|
3484
|
+
fixable.append((layer, status, msg, fixer))
|
|
3485
|
+
|
|
3486
|
+
print()
|
|
3487
|
+
print(f"Summary: {counts['OK']} OK, {counts['MISSING']} missing, "
|
|
3488
|
+
f"{counts['STALE']} stale, {counts['FAIL']} failed, {counts['WARN']} warned.")
|
|
3489
|
+
|
|
3490
|
+
if fixable and fix and not dry_run:
|
|
3491
|
+
print()
|
|
3492
|
+
print(f"Auto-fixing {len(fixable)} item(s):")
|
|
3493
|
+
for layer, status, msg, fixer in fixable:
|
|
3494
|
+
try:
|
|
3495
|
+
outcome = fixer()
|
|
3496
|
+
print(f" ✓ [{layer}] {outcome}")
|
|
3497
|
+
except Exception as e:
|
|
3498
|
+
print(f" ✗ [{layer}] fixer raised: {e}")
|
|
3499
|
+
elif fixable and not fix:
|
|
3500
|
+
print()
|
|
3501
|
+
print(f"{len(fixable)} item(s) can be auto-fixed. Re-run with --audit --fix to apply.")
|
|
3502
|
+
elif fixable and dry_run:
|
|
3503
|
+
print()
|
|
3504
|
+
print(f"(dry-run) {len(fixable)} item(s) would be fixed.")
|
|
3505
|
+
|
|
3506
|
+
|
|
3507
|
+
def _parse_hub_spec(spec: str):
|
|
3508
|
+
"""Parse '--hub name=host[:port][,ssh=h][,key=p][,priority=1]' into HubSpec."""
|
|
3509
|
+
from seif.context.host_init import HubSpec
|
|
3510
|
+
if "=" not in spec:
|
|
3511
|
+
raise ValueError(f"hub spec missing 'name=': {spec!r}")
|
|
3512
|
+
name, _, rest = spec.partition("=")
|
|
3513
|
+
name = name.strip()
|
|
3514
|
+
if not name:
|
|
3515
|
+
raise ValueError(f"hub spec has empty name: {spec!r}")
|
|
3516
|
+
parts = [p.strip() for p in rest.split(",") if p.strip()]
|
|
3517
|
+
if not parts:
|
|
3518
|
+
raise ValueError(f"hub spec missing host: {spec!r}")
|
|
3519
|
+
host_part = parts[0]
|
|
3520
|
+
if ":" in host_part:
|
|
3521
|
+
host, _, port_s = host_part.partition(":")
|
|
3522
|
+
port = int(port_s)
|
|
3523
|
+
else:
|
|
3524
|
+
host, port = host_part, 7332
|
|
3525
|
+
extras = {}
|
|
3526
|
+
for p in parts[1:]:
|
|
3527
|
+
if "=" not in p:
|
|
3528
|
+
continue
|
|
3529
|
+
k, _, v = p.partition("=")
|
|
3530
|
+
extras[k.strip()] = v.strip()
|
|
3531
|
+
return HubSpec(
|
|
3532
|
+
name=name, host=host, port=port,
|
|
3533
|
+
health_port=int(extras.get("health_port", port + 2)),
|
|
3534
|
+
ssh_host=extras.get("ssh"),
|
|
3535
|
+
ssh_key=extras.get("key"),
|
|
3536
|
+
priority=int(extras.get("priority", 1)),
|
|
3537
|
+
transport=extras.get("transport", "direct"),
|
|
3538
|
+
)
|
|
3539
|
+
|
|
3540
|
+
|
|
3541
|
+
def cmd_init_host(label=None, role="dev", overwrite_machine_id=False,
|
|
3542
|
+
hub_specs=None, source_specs=None, dry_run=False):
|
|
3543
|
+
"""Initialize host-specific files (~/.seif/machine_id + optional bridge/sources)."""
|
|
3544
|
+
try:
|
|
3545
|
+
from seif.context.host_init import (
|
|
3546
|
+
init_host, get_machine_id_path, get_bridge_config_path, get_sources_path,
|
|
3547
|
+
)
|
|
3548
|
+
except ImportError:
|
|
3549
|
+
print("Installation error: seif.context.host_init not found.")
|
|
3550
|
+
return
|
|
3551
|
+
|
|
3552
|
+
# Parse hub specs
|
|
3553
|
+
hubs = []
|
|
3554
|
+
for spec in (hub_specs or []):
|
|
3555
|
+
try:
|
|
3556
|
+
hubs.append(_parse_hub_spec(spec))
|
|
3557
|
+
except (ValueError, TypeError) as e:
|
|
3558
|
+
print(f"Invalid --hub spec {spec!r}: {e}")
|
|
3559
|
+
return
|
|
3560
|
+
|
|
3561
|
+
# Validate source specs early
|
|
3562
|
+
if source_specs:
|
|
3563
|
+
from seif.context.host_init import parse_source_spec
|
|
3564
|
+
for spec in source_specs:
|
|
3565
|
+
try:
|
|
3566
|
+
parse_source_spec(spec)
|
|
3567
|
+
except ValueError as e:
|
|
3568
|
+
print(f"Invalid --source spec {spec!r}: {e}")
|
|
3569
|
+
return
|
|
3570
|
+
|
|
3571
|
+
print(f"Host directory: {get_machine_id_path().parent}")
|
|
3572
|
+
if dry_run:
|
|
3573
|
+
print("(dry-run — no files will be written)")
|
|
3574
|
+
print()
|
|
3575
|
+
|
|
3576
|
+
result = init_host(
|
|
3577
|
+
label=label, role=role,
|
|
3578
|
+
overwrite_machine_id=overwrite_machine_id,
|
|
3579
|
+
hubs=hubs or None,
|
|
3580
|
+
source_specs=source_specs or None,
|
|
3581
|
+
dry_run=dry_run,
|
|
3582
|
+
)
|
|
3583
|
+
|
|
3584
|
+
mid = result.machine_id
|
|
3585
|
+
print("[machine_id]")
|
|
3586
|
+
print(f" Status: {'created' if result.machine_id_created else 'reused'}")
|
|
3587
|
+
print(f" Hostname: {mid['hostname']}")
|
|
3588
|
+
print(f" Label: {mid['label']}")
|
|
3589
|
+
print(f" Role: {mid['role']}")
|
|
3590
|
+
print(f" Fingerprint: {mid['fingerprint']}")
|
|
3591
|
+
if not dry_run:
|
|
3592
|
+
print(f" File: {get_machine_id_path()}")
|
|
3593
|
+
print()
|
|
3594
|
+
|
|
3595
|
+
if result.bridge_config is not None:
|
|
3596
|
+
print("[bridge-config.json]")
|
|
3597
|
+
for h in result.bridge_config["hubs"]:
|
|
3598
|
+
ssh = f" via ssh={h.get('ssh_host')}" if h.get("ssh_host") else ""
|
|
3599
|
+
print(f" - {h['name']}: {h['host']}:{h['port']}{ssh}")
|
|
3600
|
+
if not dry_run:
|
|
3601
|
+
print(f" File: {get_bridge_config_path()}")
|
|
3602
|
+
print()
|
|
3603
|
+
|
|
3604
|
+
if result.sources:
|
|
3605
|
+
print("[sources.json]")
|
|
3606
|
+
for s in result.sources:
|
|
3607
|
+
print(f" - {s['repo']:<45} {s['type']:<8} {s['classification']}")
|
|
3608
|
+
if result.sources_added:
|
|
3609
|
+
print(f" Added {result.sources_added} new source(s).")
|
|
3610
|
+
if not dry_run:
|
|
3611
|
+
print(f" File: {get_sources_path()}")
|
|
3612
|
+
print()
|
|
3613
|
+
|
|
3614
|
+
if dry_run:
|
|
3615
|
+
print("Re-run without --dry-run to persist.")
|
|
3616
|
+
|
|
3617
|
+
|
|
3618
|
+
def cmd_probe_models(machine_label: str = "local",
|
|
3619
|
+
output_format: str = "summary",
|
|
3620
|
+
dry_run: bool = False):
|
|
3621
|
+
"""Probe model orchestra and regenerate ~/.seif/model_registry.json."""
|
|
3622
|
+
try:
|
|
3623
|
+
from seif.context.model_probe import (
|
|
3624
|
+
probe_all, merge_cached_remote, write_registry,
|
|
3625
|
+
format_orchestra, get_registry_path,
|
|
3626
|
+
)
|
|
3627
|
+
except ImportError:
|
|
3628
|
+
print("Installation error: seif.context.model_probe not found.")
|
|
3629
|
+
return
|
|
3630
|
+
import json as _json
|
|
3631
|
+
|
|
3632
|
+
result = probe_all(machine_label=machine_label)
|
|
3633
|
+
merge_cached_remote(result)
|
|
3634
|
+
|
|
3635
|
+
if output_format == "json":
|
|
3636
|
+
print(_json.dumps(result.to_registry(), indent=2, ensure_ascii=False))
|
|
3637
|
+
elif output_format == "orchestra":
|
|
3638
|
+
print(format_orchestra(result))
|
|
3639
|
+
else: # summary
|
|
3640
|
+
path = get_registry_path()
|
|
3641
|
+
print(f"Probed in {result.duration_ms} ms.")
|
|
3642
|
+
print(f"Registry: {path}{' (dry-run)' if dry_run else ''}")
|
|
3643
|
+
print(f" Models discovered: {len(result.models)}")
|
|
3644
|
+
cli_n = sum(1 for m in result.models if m['id'].startswith('cli/'))
|
|
3645
|
+
ide_n = sum(1 for m in result.models if m['id'].startswith('ide/'))
|
|
3646
|
+
ollama_n = sum(1 for m in result.models if m['id'].startswith('ollama/'))
|
|
3647
|
+
api_n = sum(1 for m in result.models
|
|
3648
|
+
if any(m['id'].startswith(p) for p in ('xai/', 'anthropic/', 'google/', 'openai/')))
|
|
3649
|
+
cached_n = sum(1 for m in result.models if m['status'] == 'cached')
|
|
3650
|
+
print(f" CLI tools: {cli_n}")
|
|
3651
|
+
print(f" IDE: {ide_n}")
|
|
3652
|
+
print(f" Ollama local: {ollama_n}")
|
|
3653
|
+
print(f" API keys: {api_n}")
|
|
3654
|
+
print(f" Cached remote:{cached_n}")
|
|
3655
|
+
c = result.circuit
|
|
3656
|
+
if c.get("connected"):
|
|
3657
|
+
print(f" Circuit: connected ({c.get('machine','?')}/{c.get('transport','?')})")
|
|
3658
|
+
else:
|
|
3659
|
+
print(f" Circuit: offline")
|
|
3660
|
+
|
|
3661
|
+
if not dry_run:
|
|
3662
|
+
write_registry(result)
|
|
3663
|
+
|
|
3664
|
+
|
|
3665
|
+
def cmd_rebuild_registry(scan_roots: list[str], dry_run: bool = False):
|
|
3666
|
+
"""Rebuild ~/.seif/registry.json from filesystem scan.
|
|
3667
|
+
|
|
3668
|
+
Drops stale entries (missing paths, ephemeral tempdirs) and discovers any
|
|
3669
|
+
.seif/ workspaces under the given scan_roots that are not yet registered.
|
|
3670
|
+
"""
|
|
3671
|
+
try:
|
|
3672
|
+
from seif.context.registry import (
|
|
3673
|
+
rebuild_registry,
|
|
3674
|
+
load_registry,
|
|
3675
|
+
get_registry_path,
|
|
3676
|
+
)
|
|
3677
|
+
except ImportError:
|
|
3678
|
+
print("Installation error: seif.context.registry not found.")
|
|
3679
|
+
return
|
|
3680
|
+
|
|
3681
|
+
# If no scan roots given, derive from existing registry: parents of known
|
|
3682
|
+
# contexts make natural search roots.
|
|
3683
|
+
if not scan_roots:
|
|
3684
|
+
existing = load_registry().get("contexts", [])
|
|
3685
|
+
derived = set()
|
|
3686
|
+
for ctx in existing:
|
|
3687
|
+
try:
|
|
3688
|
+
from pathlib import Path as _P
|
|
3689
|
+
p = _P(ctx["path"]).parent.parent
|
|
3690
|
+
if p.exists() and "/var/folders/" not in str(p):
|
|
3691
|
+
derived.add(str(p))
|
|
3692
|
+
except (KeyError, TypeError):
|
|
3693
|
+
continue
|
|
3694
|
+
if not derived:
|
|
3695
|
+
print("No --scan-root given and no usable parents in existing registry.")
|
|
3696
|
+
print("Provide one or more roots, e.g.:")
|
|
3697
|
+
print(" seif --rebuild-registry --scan-root /Volumes/DockerData")
|
|
3698
|
+
return
|
|
3699
|
+
scan_roots = sorted(derived)
|
|
3700
|
+
|
|
3701
|
+
print(f"Registry: {get_registry_path()}")
|
|
3702
|
+
print(f"Scan roots:")
|
|
3703
|
+
for r in scan_roots:
|
|
3704
|
+
print(f" - {r}")
|
|
3705
|
+
if dry_run:
|
|
3706
|
+
print("(dry-run — no changes will be written)")
|
|
3707
|
+
print()
|
|
3708
|
+
|
|
3709
|
+
result = rebuild_registry(scan_roots, dry_run=dry_run)
|
|
3710
|
+
|
|
3711
|
+
print(f"Kept : {result['kept']:>4} (existing entries with valid paths)")
|
|
3712
|
+
print(f"Dropped : {result['dropped']:>4} (stale or ephemeral)")
|
|
3713
|
+
print(f"Added : {result['added']:>4} (newly discovered)")
|
|
3714
|
+
print(f"Total : {result['total']:>4}")
|
|
3715
|
+
|
|
3716
|
+
if result["added_entries"]:
|
|
3717
|
+
print()
|
|
3718
|
+
print("Added contexts:")
|
|
3719
|
+
for e in result["added_entries"]:
|
|
3720
|
+
print(f" + {e['name']:<25} {e['path']}")
|
|
3721
|
+
if result["dropped_entries"]:
|
|
3722
|
+
print()
|
|
3723
|
+
print(f"Dropped {len(result['dropped_entries'])} entries (first 5 shown):")
|
|
3724
|
+
for e in result["dropped_entries"][:5]:
|
|
3725
|
+
name = e.get("name", "?")
|
|
3726
|
+
path = e.get("path", "?")
|
|
3727
|
+
print(f" - {name:<25} {path}")
|
|
3728
|
+
|
|
3729
|
+
if dry_run:
|
|
3730
|
+
print()
|
|
3731
|
+
print("Re-run without --dry-run to write changes.")
|
|
3732
|
+
|
|
3733
|
+
|
|
3308
3734
|
def cmd_status(context_repo: str = None):
|
|
3309
3735
|
"""Show detailed status of the current .seif context."""
|
|
3310
3736
|
from pathlib import Path
|
|
@@ -4114,6 +4540,58 @@ def main():
|
|
|
4114
4540
|
parser.add_argument("--all", action="store_true", help="Pipeline completo (padrão)")
|
|
4115
4541
|
parser.add_argument("--init", nargs="?", const=".", metavar="PATH",
|
|
4116
4542
|
help="Initialize S.E.I.F.: scan, detect projects, extract git, generate .seif")
|
|
4543
|
+
parser.add_argument("--rebuild-registry", action="store_true",
|
|
4544
|
+
help="Rebuild ~/.seif/registry.json by scanning the filesystem. "
|
|
4545
|
+
"Drops stale entries (missing paths, tempdirs from old test runs). "
|
|
4546
|
+
"Use --scan-root to specify directories to scan; defaults to "
|
|
4547
|
+
"the parent of any context already in the registry.")
|
|
4548
|
+
parser.add_argument("--scan-root", action="append", default=[], metavar="PATH",
|
|
4549
|
+
help="Directory to scan for .seif/ workspaces (repeatable). "
|
|
4550
|
+
"Used with --rebuild-registry.")
|
|
4551
|
+
parser.add_argument("--dry-run", action="store_true",
|
|
4552
|
+
help="Preview the operation without writing changes "
|
|
4553
|
+
"(applies to --rebuild-registry, --probe-models).")
|
|
4554
|
+
parser.add_argument("--probe-models", action="store_true",
|
|
4555
|
+
help="Probe local Ollama, installed CLI tools, API keys, and circuitd; "
|
|
4556
|
+
"regenerate ~/.seif/model_registry.json for this host. "
|
|
4557
|
+
"Per-host file (each machine sees different reachable models).")
|
|
4558
|
+
parser.add_argument("--audit-host", action="store_true",
|
|
4559
|
+
help="Audit ~/.seif/ host-level structure across the 5 architecture "
|
|
4560
|
+
"layers (A: protocol, B: init-generated, C: derived, D: per-host, "
|
|
4561
|
+
"E: heartbeat). Complementary to --audit (which checks context "
|
|
4562
|
+
"modules). With --fix-host, regenerates missing/stale files.")
|
|
4563
|
+
parser.add_argument("--fix-host", action="store_true",
|
|
4564
|
+
help="With --audit-host, automatically regenerate missing or stale files.")
|
|
4565
|
+
parser.add_argument("--init-host", action="store_true",
|
|
4566
|
+
help="Initialize host-specific files (~/.seif/machine_id, "
|
|
4567
|
+
"optionally bridge-config.json and sources.json). Idempotent: "
|
|
4568
|
+
"only writes missing fields unless --overwrite-machine-id is set. "
|
|
4569
|
+
"These files are NEVER synced between machines.")
|
|
4570
|
+
parser.add_argument("--label", metavar="LABEL",
|
|
4571
|
+
help="Machine label (e.g. 'mini-m4'). Default: derived from hostname. "
|
|
4572
|
+
"Used with --init-host.")
|
|
4573
|
+
parser.add_argument("--host-role", default="dev", choices=("production", "dev", "edge", "ephemeral"),
|
|
4574
|
+
metavar="ROLE", dest="host_role",
|
|
4575
|
+
help="Host role within the SEIF circuit. Used with --init-host. "
|
|
4576
|
+
"(Note: --role is reserved for --quality-gate's text author role.)")
|
|
4577
|
+
parser.add_argument("--overwrite-machine-id", action="store_true",
|
|
4578
|
+
help="Rotate the machine_id fingerprint (otherwise re-running --init-host "
|
|
4579
|
+
"preserves the existing fingerprint).")
|
|
4580
|
+
parser.add_argument("--hub", action="append", default=[], metavar="SPEC",
|
|
4581
|
+
help="Bridge hub spec: 'name=host[:port][,ssh=ssh-host][,key=path]'. "
|
|
4582
|
+
"Repeatable. Triggers writing bridge-config.json. "
|
|
4583
|
+
"Example: --hub 'mini-primary=100.93.186.89:7332,ssh=mini'")
|
|
4584
|
+
parser.add_argument("--source", action="append", default=[], metavar="SPEC",
|
|
4585
|
+
help="Source repo spec: 'repo[:type[:classification]]'. Type is one of "
|
|
4586
|
+
"context|research|project (default: context). Classification "
|
|
4587
|
+
"defaults to PUBLIC/INTERNAL/CONFIDENTIAL based on type. Repeatable.")
|
|
4588
|
+
parser.add_argument("--machine-label", default="local", metavar="LABEL",
|
|
4589
|
+
help="Machine label for CLI-tool entries (e.g. 'air-m1', 'mini-m4'). "
|
|
4590
|
+
"Used with --probe-models.")
|
|
4591
|
+
parser.add_argument("--format", default="summary", choices=("summary", "orchestra", "json"),
|
|
4592
|
+
metavar="FMT",
|
|
4593
|
+
help="Output format for --probe-models: summary (counts), "
|
|
4594
|
+
"orchestra (session-start block), json (full registry).")
|
|
4117
4595
|
parser.add_argument("-y", "--yes", action="store_true",
|
|
4118
4596
|
help="Skip confirmation prompt (auto-approve)")
|
|
4119
4597
|
parser.add_argument("--install-hooks", nargs="?", const=".", metavar="REPO",
|
|
@@ -4955,6 +5433,29 @@ def main():
|
|
|
4955
5433
|
auto_yes=args.yes)
|
|
4956
5434
|
return
|
|
4957
5435
|
|
|
5436
|
+
if getattr(args, "rebuild_registry", False):
|
|
5437
|
+
cmd_rebuild_registry(scan_roots=args.scan_root, dry_run=args.dry_run)
|
|
5438
|
+
return
|
|
5439
|
+
|
|
5440
|
+
if getattr(args, "probe_models", False):
|
|
5441
|
+
cmd_probe_models(machine_label=args.machine_label,
|
|
5442
|
+
output_format=args.format,
|
|
5443
|
+
dry_run=args.dry_run)
|
|
5444
|
+
return
|
|
5445
|
+
|
|
5446
|
+
if getattr(args, "init_host", False):
|
|
5447
|
+
cmd_init_host(label=args.label,
|
|
5448
|
+
role=args.host_role,
|
|
5449
|
+
overwrite_machine_id=args.overwrite_machine_id,
|
|
5450
|
+
hub_specs=args.hub,
|
|
5451
|
+
source_specs=args.source,
|
|
5452
|
+
dry_run=args.dry_run)
|
|
5453
|
+
return
|
|
5454
|
+
|
|
5455
|
+
if getattr(args, "audit_host", False):
|
|
5456
|
+
cmd_audit_host(fix=args.fix_host, dry_run=args.dry_run)
|
|
5457
|
+
return
|
|
5458
|
+
|
|
4958
5459
|
if args.sync is not None:
|
|
4959
5460
|
cmd_sync(args.sync, args.author, args.via, context_repo=args.context_repo)
|
|
4960
5461
|
return
|