parseforge 0.2.2__tar.gz → 0.2.4__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.
- parseforge-0.2.4/PKG-INFO +240 -0
- parseforge-0.2.4/README.md +190 -0
- {parseforge-0.2.2 → parseforge-0.2.4}/SPEC.md +53 -45
- parseforge-0.2.4/parseforge/__init__.py +1 -0
- parseforge-0.2.4/parseforge/cli/config.py +146 -0
- parseforge-0.2.4/parseforge/cli/main.py +787 -0
- parseforge-0.2.4/parseforge/drift.py +141 -0
- {parseforge-0.2.2 → parseforge-0.2.4}/parseforge/generation.py +6 -1
- {parseforge-0.2.2 → parseforge-0.2.4}/parseforge/naming/providers/anthropic.py +27 -7
- {parseforge-0.2.2 → parseforge-0.2.4}/parseforge/naming/providers/deepseek.py +25 -7
- {parseforge-0.2.2 → parseforge-0.2.4}/parseforge/paths.py +9 -0
- {parseforge-0.2.2 → parseforge-0.2.4}/parseforge/pipeline.py +1 -0
- {parseforge-0.2.2 → parseforge-0.2.4}/parseforge/promotion.py +3 -10
- parseforge-0.2.4/parseforge.egg-info/PKG-INFO +240 -0
- {parseforge-0.2.2 → parseforge-0.2.4}/parseforge.egg-info/SOURCES.txt +2 -0
- {parseforge-0.2.2 → parseforge-0.2.4}/parseforge.egg-info/requires.txt +13 -1
- {parseforge-0.2.2 → parseforge-0.2.4}/pyproject.toml +22 -4
- parseforge-0.2.2/PKG-INFO +0 -83
- parseforge-0.2.2/README.md +0 -42
- parseforge-0.2.2/parseforge/__init__.py +0 -1
- parseforge-0.2.2/parseforge/cli/main.py +0 -202
- parseforge-0.2.2/parseforge.egg-info/PKG-INFO +0 -83
- {parseforge-0.2.2 → parseforge-0.2.4}/LICENSE +0 -0
- {parseforge-0.2.2 → parseforge-0.2.4}/MANIFEST.in +0 -0
- {parseforge-0.2.2 → parseforge-0.2.4}/parseforge/cli/__init__.py +0 -0
- {parseforge-0.2.2 → parseforge-0.2.4}/parseforge/integration.py +0 -0
- {parseforge-0.2.2 → parseforge-0.2.4}/parseforge/naming/__init__.py +0 -0
- {parseforge-0.2.2 → parseforge-0.2.4}/parseforge/naming/assemble.py +0 -0
- {parseforge-0.2.2 → parseforge-0.2.4}/parseforge/naming/cache.py +0 -0
- {parseforge-0.2.2 → parseforge-0.2.4}/parseforge/naming/llm.py +0 -0
- {parseforge-0.2.2 → parseforge-0.2.4}/parseforge/naming/prompts.py +0 -0
- {parseforge-0.2.2 → parseforge-0.2.4}/parseforge/naming/prompts.yaml +0 -0
- {parseforge-0.2.2 → parseforge-0.2.4}/parseforge/naming/providers/__init__.py +0 -0
- {parseforge-0.2.2 → parseforge-0.2.4}/parseforge/naming/providers/cost.py +0 -0
- {parseforge-0.2.2 → parseforge-0.2.4}/parseforge/naming/providers/errors.py +0 -0
- {parseforge-0.2.2 → parseforge-0.2.4}/parseforge/naming/providers/models.py +0 -0
- {parseforge-0.2.2 → parseforge-0.2.4}/parseforge/naming/providers/models.yaml +0 -0
- {parseforge-0.2.2 → parseforge-0.2.4}/parseforge/naming/providers/text.py +0 -0
- {parseforge-0.2.2 → parseforge-0.2.4}/parseforge/naming/resolver.py +0 -0
- {parseforge-0.2.2 → parseforge-0.2.4}/parseforge/sampling/__init__.py +0 -0
- {parseforge-0.2.2 → parseforge-0.2.4}/parseforge/sampling/backends/__init__.py +0 -0
- {parseforge-0.2.2 → parseforge-0.2.4}/parseforge/sampling/backends/netmiko.py +0 -0
- {parseforge-0.2.2 → parseforge-0.2.4}/parseforge/sampling/core.py +0 -0
- {parseforge-0.2.2 → parseforge-0.2.4}/parseforge/validation.py +0 -0
- {parseforge-0.2.2 → parseforge-0.2.4}/parseforge.egg-info/dependency_links.txt +0 -0
- {parseforge-0.2.2 → parseforge-0.2.4}/parseforge.egg-info/entry_points.txt +0 -0
- {parseforge-0.2.2 → parseforge-0.2.4}/parseforge.egg-info/top_level.txt +0 -0
- {parseforge-0.2.2 → parseforge-0.2.4}/setup.cfg +0 -0
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: parseforge
|
|
3
|
+
Version: 0.2.4
|
|
4
|
+
Summary: LLM-driven pipeline that forges, validates, and promotes TextFSM templates from network CLI output
|
|
5
|
+
Author-email: Tuyen Mathew Duong <tuyen@geekstrident.com>
|
|
6
|
+
Maintainer-email: Tuyen Mathew Duong <tuyen@geekstrident.com>
|
|
7
|
+
License: MIT
|
|
8
|
+
Keywords: textfsm,network automation,cli parsing,template generator,ai,llm,netmiko,device output parsing
|
|
9
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Intended Audience :: Information Technology
|
|
12
|
+
Classifier: Intended Audience :: System Administrators
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
19
|
+
Classifier: Topic :: Software Development :: Code Generators
|
|
20
|
+
Classifier: Topic :: Text Processing
|
|
21
|
+
Classifier: Topic :: Utilities
|
|
22
|
+
Classifier: Operating System :: OS Independent
|
|
23
|
+
Requires-Python: >=3.9
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: click>=8.1
|
|
27
|
+
Requires-Dist: PyYAML>=6.0
|
|
28
|
+
Requires-Dist: textfsm>=1.1.0
|
|
29
|
+
Requires-Dist: textfsm-ai>=0.6.1
|
|
30
|
+
Provides-Extra: anthropic
|
|
31
|
+
Requires-Dist: anthropic>=0.40.0; extra == "anthropic"
|
|
32
|
+
Requires-Dist: textfsm-ai[anthropic]>=0.6.1; extra == "anthropic"
|
|
33
|
+
Provides-Extra: deepseek
|
|
34
|
+
Requires-Dist: openai>=1.0.0; extra == "deepseek"
|
|
35
|
+
Requires-Dist: textfsm-ai[deepseek]>=0.6.1; extra == "deepseek"
|
|
36
|
+
Provides-Extra: sampling
|
|
37
|
+
Requires-Dist: netmiko>=4.0; extra == "sampling"
|
|
38
|
+
Provides-Extra: dev
|
|
39
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
40
|
+
Requires-Dist: pytest-cov>=5.0.0; extra == "dev"
|
|
41
|
+
Requires-Dist: ruff>=0.4.0; extra == "dev"
|
|
42
|
+
Requires-Dist: black>=24.0.0; extra == "dev"
|
|
43
|
+
Requires-Dist: mypy>=1.10.0; extra == "dev"
|
|
44
|
+
Requires-Dist: anthropic>=0.40.0; extra == "dev"
|
|
45
|
+
Requires-Dist: openai>=1.0.0; extra == "dev"
|
|
46
|
+
Provides-Extra: release
|
|
47
|
+
Requires-Dist: bump2version; extra == "release"
|
|
48
|
+
Requires-Dist: build; extra == "release"
|
|
49
|
+
Dynamic: license-file
|
|
50
|
+
|
|
51
|
+
# ParseForge
|
|
52
|
+
|
|
53
|
+
LLM-driven pipeline that forges, cross-validates, and promotes [TextFSM](https://github.com/google/textfsm)
|
|
54
|
+
templates from network device CLI output.
|
|
55
|
+
|
|
56
|
+
Full design plan: [SPEC.md](SPEC.md).
|
|
57
|
+
|
|
58
|
+
## Status
|
|
59
|
+
|
|
60
|
+
Early beta. The full pipeline is implemented and tested end to end — naming,
|
|
61
|
+
sampling, generation, self-validation, integration (output-schema group/variant
|
|
62
|
+
clustering), promotion (auto and human-reviewed), and drift monitoring — and
|
|
63
|
+
wired into the CLI below. A few things are intentionally not there yet:
|
|
64
|
+
|
|
65
|
+
- **`USER_REVIEWED` promotion has a library entry point but no CLI command**
|
|
66
|
+
(`promotion.promote_user_reviewed()` works today; there's no
|
|
67
|
+
`parseforge promotion --mode user-reviewed` yet). Deferred until real
|
|
68
|
+
human-reviewed cases exist to show what a CLI/config shape for a list of
|
|
69
|
+
case/suffix/gate requests should actually look like, rather than guessing
|
|
70
|
+
ahead of need.
|
|
71
|
+
- **Batch sampling mode** (collect several samples per command before
|
|
72
|
+
generating, SPEC.md §4) is designed but not built — the simpler
|
|
73
|
+
per-command loop mode is the only one implemented.
|
|
74
|
+
- **One sampling connector** (Netmiko/SSH). The CLI's `--connector` registry
|
|
75
|
+
is built to hold more without a redesign, but nothing else is wired in yet.
|
|
76
|
+
|
|
77
|
+
## Installation
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
pip install parseforge[anthropic]
|
|
81
|
+
```
|
|
82
|
+
`pip install parseforge` alone installs no AI-provider SDK at all — every
|
|
83
|
+
command that's pure local processing (`canonical`/`readable`/`recognizers`,
|
|
84
|
+
`integration`, `promotion`) works with nothing further. Anything that calls an
|
|
85
|
+
LLM (`name`, `check --provider`, `run`, `generate-template`, `trial`) needs the
|
|
86
|
+
extra for whichever provider it uses: `anthropic` or `deepseek`. `--provider`
|
|
87
|
+
defaults to `anthropic` wherever it isn't required, so that's the one most
|
|
88
|
+
setups need. `pip install parseforge[sampling]` adds Netmiko for live device
|
|
89
|
+
sampling; combine extras as needed, e.g.
|
|
90
|
+
`pip install parseforge[anthropic,deepseek,sampling]`.
|
|
91
|
+
|
|
92
|
+
## Development
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
pip install -e ".[dev,sampling]"
|
|
96
|
+
pytest
|
|
97
|
+
```
|
|
98
|
+
`dev` already includes both `anthropic` and `openai` (tests exercise both
|
|
99
|
+
providers, never silently skip) — add `,anthropic`/`,deepseek` explicitly only
|
|
100
|
+
if installing outside of `dev`.
|
|
101
|
+
|
|
102
|
+
Linting/formatting/type-checking/docs run through tox instead of extras — see
|
|
103
|
+
`tox.ini` (`tox -e lint`/`format`/`typecheck`/`docs`), each installing its own
|
|
104
|
+
tools in an isolated env. Cutting a release needs `pip install -e ".[release]"`
|
|
105
|
+
(`bump2version`, `build`) — see `scripts/release.ps1`.
|
|
106
|
+
|
|
107
|
+
## CLI
|
|
108
|
+
|
|
109
|
+
Three kinds of commands: single lookups (`name`, `check`), one-shot inspection with
|
|
110
|
+
no persistence (`generate-template`, `canonical`/`readable`/`recognizers`), and the
|
|
111
|
+
config-driven `trial` → `integration` → `promotion` workflow that runs the full
|
|
112
|
+
pipeline end to end (see [Quickstart: end to end](#quickstart-end-to-end) below).
|
|
113
|
+
|
|
114
|
+
**Naming** — resolve a raw CLI command to its canonical cli-name (cached after the
|
|
115
|
+
first call, per SPEC §2):
|
|
116
|
+
```
|
|
117
|
+
parseforge name --vendor cisco --family catalyst9200 --os ios-xe --version 17.9.1 \
|
|
118
|
+
show interface GE1.1 status
|
|
119
|
+
```
|
|
120
|
+
Still needs an LLM provider on a cache miss — `--provider` defaults to `anthropic`,
|
|
121
|
+
and `--api-key` falls back to that provider's own env var (`ANTHROPIC_API_KEY`/
|
|
122
|
+
`DEEPSEEK_API_KEY`). A cache hit (a command already seen before) never touches the
|
|
123
|
+
LLM, so no key is needed at all in that case.
|
|
124
|
+
|
|
125
|
+
**`check`** — validate a connector or provider before spending time/tokens on a real
|
|
126
|
+
run. With neither `--env` nor explicit connection flags, prints what a connector needs
|
|
127
|
+
instead of attempting a connection:
|
|
128
|
+
```
|
|
129
|
+
parseforge check --connector netmiko --env cisco
|
|
130
|
+
parseforge check --provider anthropic
|
|
131
|
+
```
|
|
132
|
+
`--env=<name>` reads `<NAME>_SANDBOX_HOST/USERNAME/PASSWORD/DEVICE_TYPE` from the
|
|
133
|
+
environment (the same convention the test suite uses for `CISCO_SANDBOX_*`).
|
|
134
|
+
|
|
135
|
+
**`generate-template`** — one-shot template generation, no trial persisted under
|
|
136
|
+
`trials/`:
|
|
137
|
+
```
|
|
138
|
+
parseforge generate-template --sample-file sample.txt \
|
|
139
|
+
--provider anthropic --api-key $ANTHROPIC_API_KEY --model claude-haiku-4-5-20251001
|
|
140
|
+
```
|
|
141
|
+
Also accepts `--connector`/`--cmdline` (live sample) or `--config <file>` in place of
|
|
142
|
+
`--sample-file`; add `--out <dir>` to also write `template.textfsm`/`readable-dsl.txt`/
|
|
143
|
+
`recognizers.txt` to disk. `parseforge init-generate-template-config [--out <file>]`
|
|
144
|
+
writes a placeholder for that `--config` file, ready to fill in.
|
|
145
|
+
|
|
146
|
+
**`canonical` / `readable` / `recognizers`** — inspect an existing template file. No
|
|
147
|
+
LLM call; `--sample` is required to build the example records `textfsm-ai`'s DSL
|
|
148
|
+
compiler needs:
|
|
149
|
+
```
|
|
150
|
+
parseforge canonical template.textfsm --sample sample.txt
|
|
151
|
+
parseforge readable template.textfsm --sample sample.txt
|
|
152
|
+
parseforge recognizers template.textfsm --sample sample.txt
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
**`run`** — a single full trial (sample -> generate -> self-validate), SPEC §5 steps
|
|
156
|
+
1-7:
|
|
157
|
+
```
|
|
158
|
+
parseforge run --vendor cisco --family catalyst9200 --os ios-xe --version 17.9.1 \
|
|
159
|
+
--host 10.0.0.1 --username admin --device-type cisco_ios \
|
|
160
|
+
--provider anthropic --api-key $ANTHROPIC_API_KEY \
|
|
161
|
+
--model claude-haiku-4-5-20251001 \
|
|
162
|
+
show clock
|
|
163
|
+
```
|
|
164
|
+
`--provider`/`--api-key`/`--model` are for generation. Naming has its own separate
|
|
165
|
+
`--naming-provider`/`--naming-api-key`/`--naming-model`, defaulting independently
|
|
166
|
+
(`--naming-provider` defaults to `anthropic`; the other two fall back to that
|
|
167
|
+
provider's own env var/default model) — set them explicitly if naming needs a
|
|
168
|
+
different provider than generation.
|
|
169
|
+
|
|
170
|
+
**`init-trial-config`** — write a placeholder `trial.yaml` to fill in, instead of
|
|
171
|
+
writing one by hand:
|
|
172
|
+
```
|
|
173
|
+
parseforge init-trial-config --out trial.yaml
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
**`trial`** — every command in a YAML config file, optionally in parallel:
|
|
177
|
+
```
|
|
178
|
+
parseforge trial --config trial.yaml
|
|
179
|
+
```
|
|
180
|
+
```yaml
|
|
181
|
+
vendor: cisco
|
|
182
|
+
family: catalyst9200
|
|
183
|
+
os: ios-xe
|
|
184
|
+
version: 17.9.1
|
|
185
|
+
connector: netmiko
|
|
186
|
+
host: 10.0.0.1
|
|
187
|
+
username: admin
|
|
188
|
+
password: secret
|
|
189
|
+
device_type: cisco_ios
|
|
190
|
+
provider: anthropic
|
|
191
|
+
api_key: sk-...
|
|
192
|
+
model: claude-haiku-4-5-20251001
|
|
193
|
+
commands:
|
|
194
|
+
- show clock
|
|
195
|
+
- show version
|
|
196
|
+
user: alice
|
|
197
|
+
workers: 1
|
|
198
|
+
```
|
|
199
|
+
`provider`/`api_key`/`model` are one shared LLM source used for both naming and
|
|
200
|
+
generation. Use the `run` command's separate `--naming-*`/`--generation-*` flags
|
|
201
|
+
instead if a trial actually needs two different providers.
|
|
202
|
+
|
|
203
|
+
**`integration`** — rebuild `integration/` for every case under `trials/` (SPEC §5
|
|
204
|
+
step 8):
|
|
205
|
+
```
|
|
206
|
+
parseforge integration
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
**`promotion`** — auto-promote every group that clears its gate (SPEC §5 step 9;
|
|
210
|
+
`AUTO_PROMOTED` mode only — `USER_REVIEWED` has no CLI surface yet):
|
|
211
|
+
```
|
|
212
|
+
parseforge promotion --user alice --threshold 1.0 --min-samples 1
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
`trial`, `integration`, and `promotion` all default to `paths.DEFAULT_STORE_ROOT`
|
|
216
|
+
(`~/.parseforge/tests`); pass `--path <dir>` to point at a different store root.
|
|
217
|
+
|
|
218
|
+
### Quickstart: end to end
|
|
219
|
+
|
|
220
|
+
Run `trial` once per device/command batch (repeat as more devices or commands come
|
|
221
|
+
in — each run only adds new evidence, it never discards prior trials), then
|
|
222
|
+
`integration` and `promotion` any time you want the current evidence reflected in
|
|
223
|
+
`authoritative/`:
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
parseforge trial --config trial.yaml
|
|
227
|
+
parseforge integration
|
|
228
|
+
parseforge promotion --user alice
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
`integration` rebuilds `integration/reference-summary.json` from every trial
|
|
232
|
+
currently on disk, and `promotion` always refreshes integration itself before
|
|
233
|
+
evaluating any gate — so running `promotion` alone after a `trial` run is enough to
|
|
234
|
+
pick up new evidence; a separate `integration` run is only useful if you want to
|
|
235
|
+
inspect `reference-summary.json` without also promoting.
|
|
236
|
+
|
|
237
|
+
## Reference
|
|
238
|
+
|
|
239
|
+
- [Documentation site](https://geeks-trident-llc.github.io/parseforge/) ([source](./docs/index.md))
|
|
240
|
+
- [SPEC.md](SPEC.md) — full design plan and open questions
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# ParseForge
|
|
2
|
+
|
|
3
|
+
LLM-driven pipeline that forges, cross-validates, and promotes [TextFSM](https://github.com/google/textfsm)
|
|
4
|
+
templates from network device CLI output.
|
|
5
|
+
|
|
6
|
+
Full design plan: [SPEC.md](SPEC.md).
|
|
7
|
+
|
|
8
|
+
## Status
|
|
9
|
+
|
|
10
|
+
Early beta. The full pipeline is implemented and tested end to end — naming,
|
|
11
|
+
sampling, generation, self-validation, integration (output-schema group/variant
|
|
12
|
+
clustering), promotion (auto and human-reviewed), and drift monitoring — and
|
|
13
|
+
wired into the CLI below. A few things are intentionally not there yet:
|
|
14
|
+
|
|
15
|
+
- **`USER_REVIEWED` promotion has a library entry point but no CLI command**
|
|
16
|
+
(`promotion.promote_user_reviewed()` works today; there's no
|
|
17
|
+
`parseforge promotion --mode user-reviewed` yet). Deferred until real
|
|
18
|
+
human-reviewed cases exist to show what a CLI/config shape for a list of
|
|
19
|
+
case/suffix/gate requests should actually look like, rather than guessing
|
|
20
|
+
ahead of need.
|
|
21
|
+
- **Batch sampling mode** (collect several samples per command before
|
|
22
|
+
generating, SPEC.md §4) is designed but not built — the simpler
|
|
23
|
+
per-command loop mode is the only one implemented.
|
|
24
|
+
- **One sampling connector** (Netmiko/SSH). The CLI's `--connector` registry
|
|
25
|
+
is built to hold more without a redesign, but nothing else is wired in yet.
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
pip install parseforge[anthropic]
|
|
31
|
+
```
|
|
32
|
+
`pip install parseforge` alone installs no AI-provider SDK at all — every
|
|
33
|
+
command that's pure local processing (`canonical`/`readable`/`recognizers`,
|
|
34
|
+
`integration`, `promotion`) works with nothing further. Anything that calls an
|
|
35
|
+
LLM (`name`, `check --provider`, `run`, `generate-template`, `trial`) needs the
|
|
36
|
+
extra for whichever provider it uses: `anthropic` or `deepseek`. `--provider`
|
|
37
|
+
defaults to `anthropic` wherever it isn't required, so that's the one most
|
|
38
|
+
setups need. `pip install parseforge[sampling]` adds Netmiko for live device
|
|
39
|
+
sampling; combine extras as needed, e.g.
|
|
40
|
+
`pip install parseforge[anthropic,deepseek,sampling]`.
|
|
41
|
+
|
|
42
|
+
## Development
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
pip install -e ".[dev,sampling]"
|
|
46
|
+
pytest
|
|
47
|
+
```
|
|
48
|
+
`dev` already includes both `anthropic` and `openai` (tests exercise both
|
|
49
|
+
providers, never silently skip) — add `,anthropic`/`,deepseek` explicitly only
|
|
50
|
+
if installing outside of `dev`.
|
|
51
|
+
|
|
52
|
+
Linting/formatting/type-checking/docs run through tox instead of extras — see
|
|
53
|
+
`tox.ini` (`tox -e lint`/`format`/`typecheck`/`docs`), each installing its own
|
|
54
|
+
tools in an isolated env. Cutting a release needs `pip install -e ".[release]"`
|
|
55
|
+
(`bump2version`, `build`) — see `scripts/release.ps1`.
|
|
56
|
+
|
|
57
|
+
## CLI
|
|
58
|
+
|
|
59
|
+
Three kinds of commands: single lookups (`name`, `check`), one-shot inspection with
|
|
60
|
+
no persistence (`generate-template`, `canonical`/`readable`/`recognizers`), and the
|
|
61
|
+
config-driven `trial` → `integration` → `promotion` workflow that runs the full
|
|
62
|
+
pipeline end to end (see [Quickstart: end to end](#quickstart-end-to-end) below).
|
|
63
|
+
|
|
64
|
+
**Naming** — resolve a raw CLI command to its canonical cli-name (cached after the
|
|
65
|
+
first call, per SPEC §2):
|
|
66
|
+
```
|
|
67
|
+
parseforge name --vendor cisco --family catalyst9200 --os ios-xe --version 17.9.1 \
|
|
68
|
+
show interface GE1.1 status
|
|
69
|
+
```
|
|
70
|
+
Still needs an LLM provider on a cache miss — `--provider` defaults to `anthropic`,
|
|
71
|
+
and `--api-key` falls back to that provider's own env var (`ANTHROPIC_API_KEY`/
|
|
72
|
+
`DEEPSEEK_API_KEY`). A cache hit (a command already seen before) never touches the
|
|
73
|
+
LLM, so no key is needed at all in that case.
|
|
74
|
+
|
|
75
|
+
**`check`** — validate a connector or provider before spending time/tokens on a real
|
|
76
|
+
run. With neither `--env` nor explicit connection flags, prints what a connector needs
|
|
77
|
+
instead of attempting a connection:
|
|
78
|
+
```
|
|
79
|
+
parseforge check --connector netmiko --env cisco
|
|
80
|
+
parseforge check --provider anthropic
|
|
81
|
+
```
|
|
82
|
+
`--env=<name>` reads `<NAME>_SANDBOX_HOST/USERNAME/PASSWORD/DEVICE_TYPE` from the
|
|
83
|
+
environment (the same convention the test suite uses for `CISCO_SANDBOX_*`).
|
|
84
|
+
|
|
85
|
+
**`generate-template`** — one-shot template generation, no trial persisted under
|
|
86
|
+
`trials/`:
|
|
87
|
+
```
|
|
88
|
+
parseforge generate-template --sample-file sample.txt \
|
|
89
|
+
--provider anthropic --api-key $ANTHROPIC_API_KEY --model claude-haiku-4-5-20251001
|
|
90
|
+
```
|
|
91
|
+
Also accepts `--connector`/`--cmdline` (live sample) or `--config <file>` in place of
|
|
92
|
+
`--sample-file`; add `--out <dir>` to also write `template.textfsm`/`readable-dsl.txt`/
|
|
93
|
+
`recognizers.txt` to disk. `parseforge init-generate-template-config [--out <file>]`
|
|
94
|
+
writes a placeholder for that `--config` file, ready to fill in.
|
|
95
|
+
|
|
96
|
+
**`canonical` / `readable` / `recognizers`** — inspect an existing template file. No
|
|
97
|
+
LLM call; `--sample` is required to build the example records `textfsm-ai`'s DSL
|
|
98
|
+
compiler needs:
|
|
99
|
+
```
|
|
100
|
+
parseforge canonical template.textfsm --sample sample.txt
|
|
101
|
+
parseforge readable template.textfsm --sample sample.txt
|
|
102
|
+
parseforge recognizers template.textfsm --sample sample.txt
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**`run`** — a single full trial (sample -> generate -> self-validate), SPEC §5 steps
|
|
106
|
+
1-7:
|
|
107
|
+
```
|
|
108
|
+
parseforge run --vendor cisco --family catalyst9200 --os ios-xe --version 17.9.1 \
|
|
109
|
+
--host 10.0.0.1 --username admin --device-type cisco_ios \
|
|
110
|
+
--provider anthropic --api-key $ANTHROPIC_API_KEY \
|
|
111
|
+
--model claude-haiku-4-5-20251001 \
|
|
112
|
+
show clock
|
|
113
|
+
```
|
|
114
|
+
`--provider`/`--api-key`/`--model` are for generation. Naming has its own separate
|
|
115
|
+
`--naming-provider`/`--naming-api-key`/`--naming-model`, defaulting independently
|
|
116
|
+
(`--naming-provider` defaults to `anthropic`; the other two fall back to that
|
|
117
|
+
provider's own env var/default model) — set them explicitly if naming needs a
|
|
118
|
+
different provider than generation.
|
|
119
|
+
|
|
120
|
+
**`init-trial-config`** — write a placeholder `trial.yaml` to fill in, instead of
|
|
121
|
+
writing one by hand:
|
|
122
|
+
```
|
|
123
|
+
parseforge init-trial-config --out trial.yaml
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**`trial`** — every command in a YAML config file, optionally in parallel:
|
|
127
|
+
```
|
|
128
|
+
parseforge trial --config trial.yaml
|
|
129
|
+
```
|
|
130
|
+
```yaml
|
|
131
|
+
vendor: cisco
|
|
132
|
+
family: catalyst9200
|
|
133
|
+
os: ios-xe
|
|
134
|
+
version: 17.9.1
|
|
135
|
+
connector: netmiko
|
|
136
|
+
host: 10.0.0.1
|
|
137
|
+
username: admin
|
|
138
|
+
password: secret
|
|
139
|
+
device_type: cisco_ios
|
|
140
|
+
provider: anthropic
|
|
141
|
+
api_key: sk-...
|
|
142
|
+
model: claude-haiku-4-5-20251001
|
|
143
|
+
commands:
|
|
144
|
+
- show clock
|
|
145
|
+
- show version
|
|
146
|
+
user: alice
|
|
147
|
+
workers: 1
|
|
148
|
+
```
|
|
149
|
+
`provider`/`api_key`/`model` are one shared LLM source used for both naming and
|
|
150
|
+
generation. Use the `run` command's separate `--naming-*`/`--generation-*` flags
|
|
151
|
+
instead if a trial actually needs two different providers.
|
|
152
|
+
|
|
153
|
+
**`integration`** — rebuild `integration/` for every case under `trials/` (SPEC §5
|
|
154
|
+
step 8):
|
|
155
|
+
```
|
|
156
|
+
parseforge integration
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
**`promotion`** — auto-promote every group that clears its gate (SPEC §5 step 9;
|
|
160
|
+
`AUTO_PROMOTED` mode only — `USER_REVIEWED` has no CLI surface yet):
|
|
161
|
+
```
|
|
162
|
+
parseforge promotion --user alice --threshold 1.0 --min-samples 1
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
`trial`, `integration`, and `promotion` all default to `paths.DEFAULT_STORE_ROOT`
|
|
166
|
+
(`~/.parseforge/tests`); pass `--path <dir>` to point at a different store root.
|
|
167
|
+
|
|
168
|
+
### Quickstart: end to end
|
|
169
|
+
|
|
170
|
+
Run `trial` once per device/command batch (repeat as more devices or commands come
|
|
171
|
+
in — each run only adds new evidence, it never discards prior trials), then
|
|
172
|
+
`integration` and `promotion` any time you want the current evidence reflected in
|
|
173
|
+
`authoritative/`:
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
parseforge trial --config trial.yaml
|
|
177
|
+
parseforge integration
|
|
178
|
+
parseforge promotion --user alice
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
`integration` rebuilds `integration/reference-summary.json` from every trial
|
|
182
|
+
currently on disk, and `promotion` always refreshes integration itself before
|
|
183
|
+
evaluating any gate — so running `promotion` alone after a `trial` run is enough to
|
|
184
|
+
pick up new evidence; a separate `integration` run is only useful if you want to
|
|
185
|
+
inspect `reference-summary.json` without also promoting.
|
|
186
|
+
|
|
187
|
+
## Reference
|
|
188
|
+
|
|
189
|
+
- [Documentation site](https://geeks-trident-llc.github.io/parseforge/) ([source](./docs/index.md))
|
|
190
|
+
- [SPEC.md](SPEC.md) — full design plan and open questions
|
|
@@ -61,65 +61,73 @@ Deliberately no `<version>` segment in the path: a cli-name's output structure u
|
|
|
61
61
|
```
|
|
62
62
|
trials/<vendor>/<family>/<os>/<cli-name>/
|
|
63
63
|
<yyyymmdd-HHMMSS-shortid>/
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
records.json
|
|
73
|
-
debug.txt
|
|
74
|
-
status.txt
|
|
64
|
+
samples/
|
|
65
|
+
sample.txt
|
|
66
|
+
derive/
|
|
67
|
+
llm-template.textfsm
|
|
68
|
+
template.textfsm
|
|
69
|
+
readable-dsl.txt
|
|
70
|
+
recognizers.txt
|
|
71
|
+
summary.json
|
|
75
72
|
```
|
|
76
73
|
Keep the timestamp+shortid directories (not `result1..N`) — chronological ordering and collision-safety in batch mode come for free, and sequential numbers throw both away.
|
|
77
74
|
|
|
78
75
|
| File | Purpose |
|
|
79
76
|
|---|---|
|
|
80
|
-
| `
|
|
81
|
-
| `
|
|
82
|
-
| `
|
|
83
|
-
| `
|
|
84
|
-
| `
|
|
85
|
-
| `
|
|
86
|
-
| `recognizers.txt` | Heuristics/signatures for detecting this output type at runtime |
|
|
87
|
-
| `llm-records.json` | Structured record of the LLM interaction (prompt, model, params, timestamps) |
|
|
88
|
-
| `records.json` | Result of running this trial's `template.textfsm` against its own `input.txt` |
|
|
89
|
-
| `debug.txt` | Parse errors, warnings, retry attempts |
|
|
90
|
-
| `status.txt` | `passed`/`failed`, `llm-duration-ms`, `pipeline-duration-ms`, error summary |
|
|
77
|
+
| `samples/sample.txt` | Raw CLI output sample fed to the LLM |
|
|
78
|
+
| `derive/llm-template.textfsm` | Template as extracted from the LLM response, pre-cleanup |
|
|
79
|
+
| `derive/template.textfsm` | Cleaned, DSL-compiled TextFSM template candidate (`.textfsm` extension — recognized by TextFSM tooling/linters, unlike `textfsm.template`) |
|
|
80
|
+
| `derive/readable-dsl.txt` | Human-readable description of what the template captures |
|
|
81
|
+
| `derive/recognizers.txt` | Heuristics/signatures for detecting this output type at runtime |
|
|
82
|
+
| `summary.json` | Everything else about the run: `created_at`/`ended_at`/`duration_ms`, `passed`, `error` (when `passed` is false), `metadata` (project/username/email/description), `command_info` (vendor/family/os/version/device_type/command), `usage` (naming + generation token counts/cost), and `provider_info` (the generation provider/model) |
|
|
91
83
|
|
|
92
84
|
### 3.2 `integration/` (no human review yet)
|
|
93
85
|
|
|
94
86
|
```
|
|
95
87
|
integration/<vendor>/<family>/<os>/<cli-name>/
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
88
|
+
reference.json ← every group's clustering evidence for this cli-name
|
|
89
|
+
group1-template1.textfsm ← a distinct template variant, copied from trials/
|
|
90
|
+
group1-template2.textfsm ← another distinct template text within the same group, if any
|
|
91
|
+
group2-template1.textfsm ← a different output-schema group entirely (§6 multi-variant)
|
|
92
|
+
|
|
93
|
+
integration/
|
|
94
|
+
reference-summary.json ← project-wide match-rate ratios aggregated across every cli-name
|
|
100
95
|
```
|
|
101
96
|
|
|
102
|
-
**How
|
|
97
|
+
**How grouping actually works — don't assume one winner.** Every trial whose own `summary.json` says `passed` is clustered by the field-key signature of its parsed records — the columns `derive/template.textfsm` actually produces against `samples/sample.txt` — not by exact template text; two templates that produce the same fields land in the same group even if their regex differs. Within a group, byte-identical template texts are further merged into "variants," so `reference.json` can report `exact_template_count`/`exact_records_count` per variant. This replaces self-validation-only trust: a template that only works against its own source sample still gets clustered on its own field signature, but its group's `ratio_of_passed` (in `reference-summary.json`) reads low if no other trial agrees with it — the overfit signal a human or `promotion.py`'s gate needs, in place of a single hand-picked "winner." The original single-winner design's `artifact/` subdirectory (aggregated `records.json` across cross-validated samples, plus a diff/summary of samples the winner failed on) doesn't carry over: there's no separate evidence folder and no per-sample mismatch diff, since there's no longer a single winner for a sample to fail against — a sample that doesn't fit any existing group's field signature just becomes (or joins) a different group instead.
|
|
98
|
+
|
|
99
|
+
`reference.json` is `{total_case_count, total_passed_case_count, groups: {group_id: {keys, sample_path, group_case_count, variants: {variant_id: {template_path, exact_template_count, exact_records_count}}}}}` — a full rebuild every time integration runs, not an incremental diff, so it's always a fresh snapshot of every trial currently under `trials/.../<cli-name>/`.
|
|
103
100
|
|
|
104
|
-
`
|
|
101
|
+
`reference-summary.json` turns those counts into ratios for every cli-name in one report: `ratio_of_total` (a group/variant's share of *every* trial attempted, diluted by raw generation failures) and `ratio_of_passed` (its share of only the trials that actually passed). `promotion.py`'s gate evaluates on `ratio_of_passed`, since promotion should judge template consistency, not raw pipeline reliability.
|
|
105
102
|
|
|
106
|
-
`
|
|
103
|
+
**Grouping and `recognizers.txt` are two different mechanisms, not one.** §6's note about `recognizers.txt` enabling *one-of-many matching* is about a runtime picking the right authoritative template for new input; it plays no part in how integration itself tells variants apart. Group clustering here never reads `recognizers.txt` — it re-parses `derive/template.textfsm` against `samples/sample.txt` and clusters by the resulting field-key signature, purely evidence-gathering, before any template is authoritative. `recognizers.txt` is still generated per trial and carried through to `authoritative/` per-variant (§3.3), but what consumes it to actually dispatch between promoted variants at runtime is out of parseforge's own scope — still an open question (§7).
|
|
107
104
|
|
|
108
105
|
### 3.3 `authoritative/` (approved via human review, or confidence-gated auto-promotion)
|
|
109
106
|
|
|
110
107
|
```
|
|
111
108
|
authoritative/<vendor>/<family>/<os>/<cli-name>/
|
|
112
|
-
template.textfsm
|
|
113
|
-
|
|
109
|
+
template.textfsm ← current approved template (the primary variant)
|
|
110
|
+
template-v2.textfsm ← an additional simultaneously-valid variant (§6), if any
|
|
111
|
+
template-v2-<suffix>.textfsm ← a USER_REVIEWED snapshot of that variant, kept alongside its current version
|
|
112
|
+
recognizers.txt ← recognizer signature, one per template, same suffix rule
|
|
113
|
+
data/
|
|
114
|
+
sample.txt, records.json ← the sample + parsed output behind each template, same suffix rule
|
|
115
|
+
golden.hash ← sha256 of the most recently promoted template, regardless of variant
|
|
116
|
+
artifact.json ← who/when/mode/match-rate/source of the most recent promotion
|
|
117
|
+
drift-log.json ← rolling match rate over time per variant, against live production samples
|
|
114
118
|
history/
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
119
|
+
template-<yyyymmdd-HHMMSS>-<shortid>.textfsm ← prior content, archived whenever a promotion overwrites it with something different
|
|
120
|
+
|
|
121
|
+
authoritative/
|
|
122
|
+
authoritative-log.json ← project-wide, append-only: every promotion event ever, across every cli-name
|
|
123
|
+
authoritative-summary.json ← project-wide snapshot of the most recent promotion run
|
|
118
124
|
```
|
|
119
125
|
|
|
120
|
-
|
|
126
|
+
No per-variant subdirectory — every simultaneously-valid template for a `cli-name` (§6, hardware/firmware variance) lives directly in this one flat directory, distinguished by filename. The first-discovered variant owns the unsuffixed "current version" names; each additional variant owns a stable, permanent `template-v2.textfsm`, `template-v3.textfsm`, ... derived from its own group id and never renumbered. `golden.hash` and `artifact.json` are the one exception to per-variant filenames: both are singular and unsuffixed, always reflecting whichever promotion happened most recently regardless of which variant triggered it.
|
|
127
|
+
|
|
128
|
+
Promotion defaults to human review, but can be confidence-gated per variant: if a variant's match rate — against only the trials that actually passed, not diluted by raw generation failures — is at or near 100% with enough samples, auto-promote it; otherwise it's queued for review. Two modes cover this: **AUTO_PROMOTED** walks every case and promotes every qualifying variant unsuffixed; **USER_REVIEWED** is scoped to caller-reviewed `(case, suffix)` requests, writing a suffixed snapshot alongside whatever that variant's current auto-promoted files already are, never replacing them. This isn't a replacement for review — it's a filter that keeps the easy, unambiguous cases from waiting on a person while still requiring a human on anything uncertain.
|
|
121
129
|
|
|
122
|
-
**Drift detection** runs
|
|
130
|
+
**Drift detection** runs an authoritative template against new production samples on an ongoing basis, tracking a rolling match rate per variant in that cli-name's `drift-log.json`. When the rate drops below threshold, that failing sample is written into `trials/.../<cli-name>/` as a new run, which re-enters the pipeline at §5 step 4 and works its way back through `integration/` to a possible new `authoritative/` version — closing the loop rather than treating drift as a one-off alert.
|
|
123
131
|
|
|
124
132
|
---
|
|
125
133
|
|
|
@@ -133,7 +141,7 @@ Promotion from `integration/common-result` to `authoritative/` should default to
|
|
|
133
141
|
- Pro: Fails fast and isolates problems per-command; simpler to debug; easier to parallelize across commands.
|
|
134
142
|
- Con: Each template is generated from a single sample unless you explicitly loop multiple times per command and merge.
|
|
135
143
|
|
|
136
|
-
**Recommendation:** Build Mode 2 first as the MVP — it's the simpler pipeline and gives you per-command `
|
|
144
|
+
**Recommendation:** Build Mode 2 first as the MVP — it's the simpler pipeline and gives you per-command `summary.json` results immediately. Add Mode 1 as a config flag afterward that changes only the *sampling* stage (collect N samples per command before invoking the LLM) — the generation, storage, and validation stages stay identical between modes if you design sampling as a separable stage up front.
|
|
137
145
|
|
|
138
146
|
---
|
|
139
147
|
|
|
@@ -142,12 +150,12 @@ Promotion from `integration/common-result` to `authoritative/` should default to
|
|
|
142
150
|
1. **Input intake** — device OS/version/family, auth, command list, mode selection.
|
|
143
151
|
2. **Name generation** — tokenize each command → canonical `cli-name` per §2.
|
|
144
152
|
3. **Path resolution** — compute `<vendor>/<device-family>/<os>/<cli-name>/` per §3.
|
|
145
|
-
4. **Sampling** — connect (Netmiko/similar), run command(s), capture raw output → `trials/.../<run-id>/
|
|
146
|
-
5. **Generation** — send `
|
|
147
|
-
6. **Extraction & cleanup** — pull template from response → `
|
|
148
|
-
7. **Self-validation** — run `template.textfsm` against its own `
|
|
149
|
-
8. **Integration selection** —
|
|
150
|
-
9. **Authoritative promotion** — auto-promote if the winning candidate clears the confidence threshold across all known samples; otherwise queue for human review. On approval, copy to `authoritative/.../template.textfsm`, archive the prior
|
|
153
|
+
4. **Sampling** — connect (Netmiko/similar), run command(s), capture raw output → `trials/.../<run-id>/samples/sample.txt`.
|
|
154
|
+
5. **Generation** — send `sample.txt` (+ prior context if Mode 1) to LLM → `derive/llm-template.textfsm`, token usage recorded in `summary.json`.
|
|
155
|
+
6. **Extraction & cleanup** — pull template from response → cleaned `derive/template.textfsm`, plus `derive/readable-dsl.txt` and `derive/recognizers.txt`.
|
|
156
|
+
7. **Self-validation** — run `derive/template.textfsm` against its own `samples/sample.txt`; record `passed`/`error` in `summary.json`.
|
|
157
|
+
8. **Integration selection** — cluster every passed trial's `derive/template.textfsm` in `trials/.../<cli-name>/` by output-schema signature, cross-validated against every known `samples/sample.txt` for that `cli-name`; write `integration/.../reference.json` and one `group#-template#.textfsm` per distinct variant.
|
|
158
|
+
9. **Authoritative promotion** — auto-promote if the winning candidate clears the confidence threshold across all known samples; otherwise queue for human review. On approval, copy to `authoritative/.../template.textfsm` (or `template-v2.textfsm`, ... for an additional variant), archive the prior content to `history/` if it differs, and write `golden.hash`, `artifact.json`, and `authoritative-log.json`.
|
|
151
159
|
10. **Drift monitoring** — continuously run the authoritative template against new production samples, logging match rate to `drift-log.json`. On breach, feed the failing sample back into `trials/` (→ step 4) to generate a replacement candidate.
|
|
152
160
|
11. **Repeat/loop or batch-complete** depending on mode.
|
|
153
161
|
|
|
@@ -160,7 +168,7 @@ Promotion from `integration/common-result` to `authoritative/` should default to
|
|
|
160
168
|
Where a fourth tier is tempting but better handled as **metadata instead of a new directory layer:**
|
|
161
169
|
|
|
162
170
|
- **Quarantine / stale-but-still-serving:** when drift is detected, the authoritative template is still the one in production use — you don't want to yank it out mid-flight while a replacement works its way through the pipeline. Rather than a new top-level tier, this is a `status` field in `drift-log.json` (`ok` / `drifting` / `superseded`) on the existing authoritative entry. The template keeps serving; the status just flags that a replacement is in flight.
|
|
163
|
-
- **Archived / deprecated (EOL device-OS combos):** for template families you no longer actively maintain (e.g. an OS version taken out of production fleet-wide), this is also a status flag rather than a directory move — `history/` already retains every prior version, so "archived" just means
|
|
171
|
+
- **Archived / deprecated (EOL device-OS combos):** for template families you no longer actively maintain (e.g. an OS version taken out of production fleet-wide), this is also a status flag rather than a directory move — `history/` already retains every prior version, so "archived" just means `authoritative-log.json` stops getting new entries for that cli-name, not that files need to relocate.
|
|
164
172
|
- **Multiple simultaneously-valid templates for one `cli-name`:** if a command's output legitimately varies by hardware config (e.g. a chassis with vs. without an optional module) rather than by drift, that's not a tier problem — it's a case for storing *multiple* authoritative templates under the same `cli-name` path with distinct `recognizers.txt` signatures, and letting runtime dispatch pick the right one. Worth flagging now so `recognizers.txt` is designed to support one-of-many matching from the start rather than assuming exactly one template per `cli-name`.
|
|
165
173
|
|
|
166
174
|
**Bottom line:** keep the three directories; push the "in-between" states (drifting-but-live, archived, multi-variant) into metadata/status fields on the existing tiers. Adding more top-level directories per edge case leads to directory sprawl without adding real distinctions in *how* a template is used.
|
|
@@ -172,4 +180,4 @@ Where a fourth tier is tempting but better handled as **metadata instead of a ne
|
|
|
172
180
|
- Do you want a **registry/index file** (e.g. `catalog.json`) at the repo root listing every `<vendor>/<family>/<os>/<cli-name>` combination that exists, plus its authoritative status, for fast lookup without walking the filesystem?
|
|
173
181
|
- What **confidence threshold** (match-rate %, sample count minimum) should gate auto-promotion vs. human review in step 9 — worth making this configurable per-project rather than hardcoded?
|
|
174
182
|
- Should `recognizers.txt` support **one-of-many matching** from day one (per the multi-variant note in §6), or is that a v2 concern?
|
|
175
|
-
- What's the **LLM provider/model** for generation — worth pinning per-project so `
|
|
183
|
+
- What's the **LLM provider/model** for generation — worth pinning per-project so `summary.json`'s usage costs are comparable across runs?
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.4"
|