packet-tracer-skill 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +116 -0
- package/README.md +55 -9
- package/SKILL.md +214 -2
- package/package.json +2 -1
- package/scripts/build_sample_catalog.py +42 -0
- package/scripts/generate_pkt.py +4408 -187
- package/scripts/intent_parser.py +31 -4
- package/scripts/lab_coherence.py +455 -0
- package/scripts/pkt_editor.py +117 -35
- package/scripts/pkt_transformer.py +99 -1
- package/scripts/sample_catalog.py +65 -12
- package/scripts/session_log.py +325 -0
- package/scripts/usage_ledger.py +230 -218
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,122 @@ All notable changes to this project should be recorded in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is intentionally simple and release-oriented.
|
|
6
6
|
|
|
7
|
+
## [0.3.2] - 2026-09-12
|
|
8
|
+
|
|
9
|
+
### Picking up a half-finished edit session
|
|
10
|
+
|
|
11
|
+
Almost nothing here needs remembering between turns: `--doctor`,
|
|
12
|
+
`--explain-plan` and `--parity-report` all recompute from the install, the
|
|
13
|
+
donor registry and the bridge, so an agent that has lost its earlier context
|
|
14
|
+
re-runs one and is exactly as certain as before. One question had no such
|
|
15
|
+
source -- part-way through `--explain-plan` -> `--edit` -> `--parity-report`,
|
|
16
|
+
*which lab am I working on and what comes next*.
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- `output/session-log.jsonl`, one line per run, and two flags that read it
|
|
21
|
+
back: `--session-state` lists the labs in flight, `--resume <lab.pkt>` says
|
|
22
|
+
where one was left.
|
|
23
|
+
- `--resume` re-hashes the lab and compares it with what the last step
|
|
24
|
+
recorded, and claims a position **only when the two agree**. When they do
|
|
25
|
+
not it says the lab has changed and to re-derive instead -- the same
|
|
26
|
+
refusal-first stance the rest of the skill takes. Verified first that a
|
|
27
|
+
digest can carry that weight: decoding a lab and re-encoding it reproduces
|
|
28
|
+
the file byte for byte.
|
|
29
|
+
- Three constraints, each with a test. No secrets: facts are allow-listed
|
|
30
|
+
rather than filtered, because an edit prompt holds a passphrase in a field
|
|
31
|
+
called `passphrase` and a deny-list would leak whatever secret field is added
|
|
32
|
+
next; the prompt survives only as a non-reversible shape fingerprint. No
|
|
33
|
+
weight: a test regenerates a lab with and without the log and compares the
|
|
34
|
+
decoded content. Ships nowhere: `output/` is gitignored and in no
|
|
35
|
+
`package.json` file list, checked rather than assumed.
|
|
36
|
+
|
|
37
|
+
### Fixed
|
|
38
|
+
|
|
39
|
+
- `PKT_USAGE_LEDGER=on`, the obvious way to switch learning on, made the path
|
|
40
|
+
resolver treat `"on"` as a filename and write the ledger to a file called
|
|
41
|
+
`on` in the working directory. One variable carrying two meanings with only
|
|
42
|
+
one of its two readers knowing the switch words. Found by doing it.
|
|
43
|
+
- `--session-state` and `--resume` were recorded as steps by the same wrapper
|
|
44
|
+
as the work they report on, so each recovery pushed itself in front of the
|
|
45
|
+
real chain. In a bounded log a reader that writes eventually evicts the steps
|
|
46
|
+
it exists to report.
|
|
47
|
+
|
|
48
|
+
### Changed
|
|
49
|
+
|
|
50
|
+
- `main()` is split into `_build_parser`, `_dispatch` and a wrapper that
|
|
51
|
+
records once around the whole dispatch. Fourteen branches with twelve return
|
|
52
|
+
points is not somewhere to repeat a hook.
|
|
53
|
+
|
|
54
|
+
847 passed, 1 skipped. Corpus: 32 of 33 generated, 31 opened, 0 unexpected.
|
|
55
|
+
|
|
56
|
+
## [0.3.1] - 2026-09-06
|
|
57
|
+
|
|
58
|
+
### Wireless labs that actually carry traffic
|
|
59
|
+
|
|
60
|
+
`0.3.0` shipped wireless topologies that opened and passed every static check
|
|
61
|
+
while no client could reach anything. Measured on a generated lab after this
|
|
62
|
+
release: a laptop takes its lease from the home router and pings the gateway
|
|
63
|
+
and the other laptop 4/4, over Wi-Fi, on an open network and on WPA2.
|
|
64
|
+
|
|
65
|
+
Six facts about one wireless lab were each decided in two places with nothing
|
|
66
|
+
comparing them, and every one of them had to be fixed before a packet moved.
|
|
67
|
+
|
|
68
|
+
### Fixed
|
|
69
|
+
|
|
70
|
+
- **The access point's key was written where Packet Tracer does not read it.**
|
|
71
|
+
A working WPA2 home router keeps `WIRELESS_COMMON/WEP_PROCESS/KEY` with
|
|
72
|
+
`WEP_PROCESS/ENCRYPTION`, and carries no `WPA_PASSPHRASE` at all -- the field
|
|
73
|
+
names are legacy and WPA2 uses them. Choosing the field by authentication
|
|
74
|
+
type left the access point running WPA2 with no key while its clients had
|
|
75
|
+
one. Both sides looked right in every field anyone was reading.
|
|
76
|
+
- **A home router's DHCP lease records were deleted when its LAN moved.** That
|
|
77
|
+
is how a client gets its address back when the file opens; they are
|
|
78
|
+
renumbered into the new pool now, and a lease naming a client the prune
|
|
79
|
+
removed is dropped.
|
|
80
|
+
- **The client's security, network name and addressing mode never followed the
|
|
81
|
+
access point.** `set_wireless_ssid` wrote the access point;
|
|
82
|
+
`associate_wireless_client` wrote only the SSID. A repair pass now carries
|
|
83
|
+
authentication, encryption, key and network type across, and only into the
|
|
84
|
+
live profile -- every working client leaves its saved `PROFILES` list as the
|
|
85
|
+
donor's boilerplate.
|
|
86
|
+
- **A home router served whatever network its donor had.** The laptops were
|
|
87
|
+
addressed on the planned network and the router on the donor's; the router
|
|
88
|
+
now moves onto the one its own clients point at, and its pool with it.
|
|
89
|
+
- **The layout placed wireless clients out of radio range.** Hosts were laid
|
|
90
|
+
out in rows under the switch they hang off, and a wireless client hangs off
|
|
91
|
+
nothing -- one landed 420 units from a router whose radio reaches 250.
|
|
92
|
+
- **`port_exists` accepted interface names a home router does not have.** It
|
|
93
|
+
checked only the port index, so `FastEthernet0/1` passed on a device whose
|
|
94
|
+
sockets are `Ethernet 1` .. `4`, and with no interfaces in its configuration
|
|
95
|
+
`Ethernet 99` passed too. Both models were read off the live devices; the
|
|
96
|
+
repair pass reads the same list, so a wrong name is renamed rather than the
|
|
97
|
+
cable dropped.
|
|
98
|
+
- **The coherence checker could not see a home router's LAN address.** It walks
|
|
99
|
+
running configs, and that address is a setting, so it reported
|
|
100
|
+
`gateway_answers_for_nobody` against a router holding exactly that address.
|
|
101
|
+
|
|
102
|
+
### Changed
|
|
103
|
+
|
|
104
|
+
- The committed sample catalogue is limited to the labs it may publish.
|
|
105
|
+
Rebuilding it on a machine with saved labs had staged 350 entries naming
|
|
106
|
+
their owner, with absolute paths under their home directory, into a file
|
|
107
|
+
bound for a public repository. Local labs go to a git-ignored file beside it
|
|
108
|
+
and donor ranking reads both, so nothing about donor choice changed.
|
|
109
|
+
- Comments, docs and fixtures no longer identify their evidence by the filename
|
|
110
|
+
of someone's saved lab.
|
|
111
|
+
|
|
112
|
+
### Notes on measurement
|
|
113
|
+
|
|
114
|
+
`pt_inspect_ports` immediately after a lab opens shows a wireless client as
|
|
115
|
+
`up`, `linked`, `ip 0.0.0.0`. That is a first reading and means nothing -- a lab
|
|
116
|
+
Packet Tracer saved itself, seconds after it pinged 4/4, reads the same way on
|
|
117
|
+
reopen. Several hypotheses were rejected against that control rather than
|
|
118
|
+
argued: radio bandwidth matching, channel matching, `NETWORK_TYPE`, and the
|
|
119
|
+
belief that only a runtime nudge could make association work.
|
|
120
|
+
|
|
121
|
+
833 passed, 1 skipped. Corpus: 32 of 33 generated, 31 opened, 0 unexpected.
|
|
122
|
+
|
|
7
123
|
## [0.3.0] - 2026-08-06
|
|
8
124
|
|
|
9
125
|
### First verified generation
|
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ This repository is built for one job: take a natural-language network request, b
|
|
|
10
10
|
|
|
11
11
|
It is intended for networking labs where correctness matters more than producing a pretty but unverifiable diagram. The skill can plan, inspect, edit, compare, and explain Packet Tracer scenarios, but it deliberately separates "recognized by the parser", "visible in inventory", "edit-proven", "donor-backed ready", and "generate-ready" support.
|
|
12
12
|
|
|
13
|
-
## What `0.3.
|
|
13
|
+
## What `0.3.2` does
|
|
14
14
|
|
|
15
15
|
In this release a prompt produces a lab Packet Tracer opens.
|
|
16
16
|
|
|
@@ -24,11 +24,12 @@ inspecting the file.
|
|
|
24
24
|
|
|
25
25
|
| measurement | result |
|
|
26
26
|
| --- | --- |
|
|
27
|
-
| corpus scenarios generated | 32 of 33 |
|
|
28
|
-
| of those, opened by Packet Tracer |
|
|
29
|
-
| tests |
|
|
27
|
+
| corpus scenarios generated | 32 of 33 (the 33rd asks for no devices and is refused) |
|
|
28
|
+
| of those, opened by Packet Tracer | 31 of 32 |
|
|
29
|
+
| tests | 833 passed, 1 skipped |
|
|
30
30
|
| generated DHCP lab | four PCs took leases from the router pool and pinged their gateway and each other 4/4 |
|
|
31
31
|
| generated leased line | traffic crossed `Serial0/1/1 <-> Serial0/1/0` 4/4 |
|
|
32
|
+
| generated home-router lab | both hosts pinged the gateway and each other 4/4, cabled and over Wi-Fi, open and WPA2 |
|
|
32
33
|
|
|
33
34
|
Three defects had made every generated WAN lab unopenable, each hiding the next.
|
|
34
35
|
A donor the selector had *rejected* still rewrote the request, so a planned
|
|
@@ -43,6 +44,14 @@ donor-prune generation: a real lab is pruned and rewired to match the request.
|
|
|
43
44
|
The atlas `generate_ready` count further down is a stricter per-feature
|
|
44
45
|
acceptance gate, still `0` by design.
|
|
45
46
|
|
|
47
|
+
**One thing to know when you check a Wi-Fi lab.** Read a wireless client once,
|
|
48
|
+
straight after the file opens, and it will look broken: port `up` and `linked`,
|
|
49
|
+
`ip 0.0.0.0`, the radio at its un-negotiated rate. That is a first reading and
|
|
50
|
+
means nothing -- the client has not finished taking its lease. Read it again and
|
|
51
|
+
it holds its address and pings. A lab Packet Tracer saved itself, seconds after
|
|
52
|
+
it pinged 4/4, reads the same way on reopen, which is how we know the reading
|
|
53
|
+
rather than the lab is at fault.
|
|
54
|
+
|
|
46
55
|
The previous line, the `0.2.3` capability release, was focused on:
|
|
47
56
|
|
|
48
57
|
- donor-backed and scenario-aware public messaging
|
|
@@ -220,6 +229,34 @@ Launch references:
|
|
|
220
229
|
- [docs/security-edge-deepening-proof.md](docs/security-edge-deepening-proof.md)
|
|
221
230
|
- [docs/packet-tracer-feature-gap-atlas.md](docs/packet-tracer-feature-gap-atlas.md)
|
|
222
231
|
|
|
232
|
+
## Picking Up A Half-Finished Edit Session
|
|
233
|
+
|
|
234
|
+
Almost nothing in this skill needs remembering between turns. `--doctor`,
|
|
235
|
+
`--explain-plan` and `--parity-report` recompute from the install, the donor
|
|
236
|
+
registry and the bridge, so an agent that has lost its earlier context re-runs
|
|
237
|
+
one and is exactly as certain as before.
|
|
238
|
+
|
|
239
|
+
One question has no such source: part-way through
|
|
240
|
+
`--explain-plan` -> `--edit` -> `--parity-report`, *which lab am I working on
|
|
241
|
+
and what comes next*. Each run now appends a line to
|
|
242
|
+
`output/session-log.jsonl`, and two flags read it back:
|
|
243
|
+
|
|
244
|
+
```powershell
|
|
245
|
+
python scripts\generate_pkt.py --session-state
|
|
246
|
+
python scripts\generate_pkt.py --resume output\lab.pkt
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
`--resume` re-hashes the lab and compares it with what the last step recorded.
|
|
250
|
+
A position is reported **only when the two agree**; otherwise it says the lab
|
|
251
|
+
has changed and to re-derive instead. That refusal is the useful answer, and it
|
|
252
|
+
is the same stance the rest of the skill takes when it cannot prove something.
|
|
253
|
+
|
|
254
|
+
The log carries no secrets (facts are allow-listed, and the prompt survives
|
|
255
|
+
only as a non-reversible shape fingerprint), carries no weight (a test
|
|
256
|
+
regenerates a lab with and without it and compares the decoded content), and
|
|
257
|
+
ships nowhere (`output/` is gitignored and in no `package.json` file list).
|
|
258
|
+
`PKT_SESSION_LOG=off` disables it.
|
|
259
|
+
|
|
223
260
|
## Runtime Doctor Contract
|
|
224
261
|
|
|
225
262
|
`--doctor` is a product surface, not a debug afterthought. It reports:
|
|
@@ -589,7 +626,7 @@ See also:
|
|
|
589
626
|
|
|
590
627
|
## Release and Launch State
|
|
591
628
|
|
|
592
|
-
The current line is `packet-tracer-skill@0.3.
|
|
629
|
+
The current line is `packet-tracer-skill@0.3.2`, and it is the first release
|
|
593
630
|
where generation is the headline rather than a deferred promise. `0.2.3`, the
|
|
594
631
|
previous published line, was a capability proof and readiness release that
|
|
595
632
|
deliberately refused broad generation.
|
|
@@ -643,21 +680,30 @@ qurur, mövcud faylı redaktə edir və hər iddiasını ölçü ilə əsasland
|
|
|
643
680
|
fayl Packet Tracer-də açılır və cihazları bir-birini ping edir; alınmayanda
|
|
644
681
|
səbəbini açıq deyir.
|
|
645
682
|
|
|
646
|
-
### `0.3.
|
|
683
|
+
### `0.3.2` nə dəyişdi
|
|
647
684
|
|
|
648
685
|
Bu, promptun Packet Tracer-in açdığı fayla çevrildiyi ilk buraxılışdır.
|
|
649
686
|
|
|
650
687
|
| Ölçü | Nəticə |
|
|
651
688
|
| --- | --- |
|
|
652
|
-
| korpusda qurulan ssenari | 33-dən 32 |
|
|
653
|
-
| onlardan Packet Tracer-in açdığı | 32-dən
|
|
654
|
-
| testlər |
|
|
689
|
+
| korpusda qurulan ssenari | 33-dən 32 (33-cü heç bir cihaz istəmir, rədd edilir) |
|
|
690
|
+
| onlardan Packet Tracer-in açdığı | 32-dən 31 |
|
|
691
|
+
| testlər | 833 keçdi, 1 ötürüldü |
|
|
655
692
|
| DHCP laboratoriyası | 4 kompüter routerin hovuzundan ünvan aldı, şlüzə və bir-birinə 4/4 ping |
|
|
656
693
|
| icarə xətti (leased line) | trafik `Serial0/1/1 <-> Serial0/1/0` üzərindən 4/4 keçdi |
|
|
694
|
+
| ev routeri laboratoriyası | hər iki host şlüzə və bir-birinə 4/4 ping etdi -- kabel və Wi-Fi, açıq və WPA2 |
|
|
657
695
|
|
|
658
696
|
Ping rəqəmləri cihazların özündə `ping` işlədilməklə alınıb. Bu layihədə bütün
|
|
659
697
|
statik yoxlamaları keçən, amma heç nəyin ping etmədiyi laboratoriyalar olub.
|
|
660
698
|
|
|
699
|
+
**Wi-Fi laboratoriyasını yoxlayarkən bilməli olduğunuz bir şey.** Fayl açılan
|
|
700
|
+
kimi simsiz klienti bir dəfə oxusanız, xarab görünəcək: port `up` və `linked`,
|
|
701
|
+
`ip 0.0.0.0`, radio danışılmamış sürətdə. Bu **ilk oxunuşdur** və heç nə demir --
|
|
702
|
+
klient hələ icarəsini almayıb. İkinci dəfə oxuyun: ünvanı olur və ping edir.
|
|
703
|
+
Packet Tracer-in özünün yaddaşa verdiyi, saniyələr əvvəl 4/4 ping edən fayl da
|
|
704
|
+
yenidən açılanda eyni cür oxunur -- səhv olanın lab yox, oxunuş olduğunu bundan
|
|
705
|
+
bilirik.
|
|
706
|
+
|
|
661
707
|
### Nə düzəldildi
|
|
662
708
|
|
|
663
709
|
Generasiya edilən hər WAN laboratoriyası açılmırdı və bunun arxasında bir-birini
|
package/SKILL.md
CHANGED
|
@@ -37,6 +37,210 @@ Useful ideas from `MCP-Packet-Tracer` were adopted only at the architecture
|
|
|
37
37
|
level. PTBuilder live deploy and external donor usage were intentionally not
|
|
38
38
|
adopted.
|
|
39
39
|
|
|
40
|
+
## Using This Skill Correctly
|
|
41
|
+
|
|
42
|
+
Read this before generating anything. Every rule below was learned by measuring
|
|
43
|
+
a lab that looked finished and was not.
|
|
44
|
+
|
|
45
|
+
### A lab that opens is not a lab that works
|
|
46
|
+
|
|
47
|
+
This is the single most expensive mistake available here. Generated labs have
|
|
48
|
+
passed every structural check, opened cleanly in Packet Tracer, read correctly
|
|
49
|
+
line by line, and been unable to pass a single packet. "It opened" tells you
|
|
50
|
+
the container is valid. It tells you nothing about the network.
|
|
51
|
+
|
|
52
|
+
Two things separate a finished lab from a plausible one:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
python scripts/generate_pkt.py --coherence-report output/lab.pkt
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
and a live ping. Do both. `--coherence-report` exits non-zero when the lab
|
|
59
|
+
contradicts itself, and generation prints the same summary as a `WARNING:` line
|
|
60
|
+
when it hands the file over — never ignore that line.
|
|
61
|
+
|
|
62
|
+
### What the coherence report is telling you
|
|
63
|
+
|
|
64
|
+
Every defect this project has paid for has one shape: **a fact derived twice,
|
|
65
|
+
in two passes, with nothing comparing the derivations.** Each half reads
|
|
66
|
+
correctly on its own. The report is that comparison:
|
|
67
|
+
|
|
68
|
+
| Finding | What it means |
|
|
69
|
+
|---|---|
|
|
70
|
+
| `interface_declared_twice` | IOS keeps the last block; readers scan the first |
|
|
71
|
+
| `port_not_on_device` | Packet Tracer refuses to open the file |
|
|
72
|
+
| `port_double_booked` | two cables on one socket |
|
|
73
|
+
| `duplicate_address` | two interfaces claim one address |
|
|
74
|
+
| `real_address_is_also_virtual` | a router holds an address that is someone's HSRP virtual |
|
|
75
|
+
| `native_vlan_mismatch` | spanning tree blocks a cabled, configured port |
|
|
76
|
+
| `etherchannel_peer_does_not_bundle` | the switch behind it drops off the network |
|
|
77
|
+
| `gateway_answers_for_nobody` | a static host points at an address nothing holds |
|
|
78
|
+
| `pool_without_interface` | DHCP hands out addresses nothing can route |
|
|
79
|
+
|
|
80
|
+
The report never repairs. A checker that fixes what it finds stops being able
|
|
81
|
+
to tell you whether the thing it checks is working.
|
|
82
|
+
|
|
83
|
+
### Measuring in a live Packet Tracer
|
|
84
|
+
|
|
85
|
+
Three steps, in order, every time:
|
|
86
|
+
|
|
87
|
+
1. **Confirm which document is open.** With two windows open the bridge answers
|
|
88
|
+
for the other one. A pass rate measured against the wrong lab is worse than
|
|
89
|
+
no measurement, because it is believed. Check the device list first.
|
|
90
|
+
2. **Throw the first reading away.** Spanning tree has not converged; the first
|
|
91
|
+
ping after opening a large lab reports 0/4 on a path that works. It is not a
|
|
92
|
+
measurement, it is the lab starting up.
|
|
93
|
+
3. **Then measure.** A *failing* ping takes far longer than a passing one --
|
|
94
|
+
each packet waits out its own timeout, about 13 seconds for four -- so
|
|
95
|
+
budget 45-60s per call rather than reading a timeout as a failure.
|
|
96
|
+
|
|
97
|
+
Ping from a host. Routers and switches answer through a different bridge path
|
|
98
|
+
that is not reliable here.
|
|
99
|
+
|
|
100
|
+
### When two repairs in a row do not change the measurement
|
|
101
|
+
|
|
102
|
+
Stop repairing. The symptom is not where the defect is. Find the pass that
|
|
103
|
+
wrote the state, and fix it there. The fastest way in: **diff the broken device
|
|
104
|
+
against a working sibling.** One switch unreachable while identical ones work
|
|
105
|
+
has one extra line, and reading configuration top to bottom will not find it as
|
|
106
|
+
quickly as `diff` will.
|
|
107
|
+
|
|
108
|
+
### Facts about Packet Tracer that cost a lab each
|
|
109
|
+
|
|
110
|
+
- a copper cable in a fibre socket is **dropped in silence** -- the file opens,
|
|
111
|
+
the cable is simply not there
|
|
112
|
+
- a duplicate `interface` block is applied last-wins, while readers scan the first
|
|
113
|
+
- a subinterface `ip address` before its `encapsulation dot1Q` is refused
|
|
114
|
+
silently
|
|
115
|
+
- `PORT_DHCP_ENABLE=true` makes Packet Tracer ignore the static address in the
|
|
116
|
+
file; a host with both is a DHCP client, and judging it on its stale address
|
|
117
|
+
is a false reading
|
|
118
|
+
- DHCP snooping with no trusted uplink eats every offer the router sends
|
|
119
|
+
- port security on a trunk isolates the whole switch behind it
|
|
120
|
+
- a `channel-group` whose peer does not bundle takes that switch off the network
|
|
121
|
+
- an interface name the device does not own blocks the file from opening at all;
|
|
122
|
+
a double-booked port does not
|
|
123
|
+
- a `vlan N` line in a switch's configuration does **not** create the VLAN --
|
|
124
|
+
Packet Tracer keeps the database separately, and a port assigned to a VLAN
|
|
125
|
+
that is not in it forwards nothing
|
|
126
|
+
- a top-level command written straight after an indented sub-block line is
|
|
127
|
+
swallowed; the `!` between them is load-bearing
|
|
128
|
+
- a home router's sockets are named, not slotted, and the spelling is the
|
|
129
|
+
model's: `Ethernet 1` .. `4` on `WirelessRouter`, `GigabitEthernet 1` .. `4`
|
|
130
|
+
on `WirelessRouterNewGeneration`, and `Internet` for the uplink on both. It
|
|
131
|
+
writes no interfaces into its configuration, so nothing else can tell you
|
|
132
|
+
- a home router's LAN address is a **setting**, not a config line, and lives in
|
|
133
|
+
`ENGINE/LAN_IP_ADDRESS` with its pool under `ENGINE/DHCP_SERVER/POOLS/POOL`;
|
|
134
|
+
a reader that only walks running configs cannot see it
|
|
135
|
+
- a wireless client's addressing comes from its `WIRELESS_PROFILE`, not from
|
|
136
|
+
its port -- both record it, and Packet Tracer obeys the profile
|
|
137
|
+
- a radio link is made by **distance**. A client outside the access point's
|
|
138
|
+
`COVERAGERANGE` reports its port `up` and `linked` and passes nothing
|
|
139
|
+
- an access point's WPA key lives in `WIRELESS_COMMON/WEP_PROCESS/KEY` with
|
|
140
|
+
`WEP_PROCESS/ENCRYPTION`, never in `WPA_PASSPHRASE`. The names are legacy and
|
|
141
|
+
WPA2 uses them; the client side is the same shape. Put it in the wrong field
|
|
142
|
+
and the access point runs WPA2 with no key while its clients have one
|
|
143
|
+
- a wireless client reads `ip 0.0.0.0` at its un-negotiated rate for the first
|
|
144
|
+
moments after a lab opens, even in a lab Packet Tracer saved itself while it
|
|
145
|
+
was pinging 4/4. `pt_inspect_ports` right after opening is a **first reading**
|
|
146
|
+
and has to be discarded exactly like a first ping
|
|
147
|
+
- a home router's DHCP **lease records** are how a client gets its address back
|
|
148
|
+
when the file opens; move its LAN and they have to be renumbered, not dropped
|
|
149
|
+
- the builder MCP's device table is not authoritative about port names; it
|
|
150
|
+
gives the AC home router `Ethernet 1` .. `4`, and the device itself says
|
|
151
|
+
`GigabitEthernet 1` .. `4`. `pt_inspect_ports` answers at the IOS layer,
|
|
152
|
+
where those sockets are bridged into `Vlan1` and do not appear at all --
|
|
153
|
+
`pt_query_topology` is the one that lists them
|
|
154
|
+
|
|
155
|
+
### Drawing the topology yourself
|
|
156
|
+
|
|
157
|
+
The generator packs switch blocks into a roughly square grid, which is fine for
|
|
158
|
+
a lab nobody planned. When you have already decided the shape -- which
|
|
159
|
+
department sits where, which switch faces which, where the core belongs -- say
|
|
160
|
+
so in the blueprint and set `PACKET_TRACER_LAYOUT=keep`. Every `x`/`y` in the
|
|
161
|
+
blueprint is then left exactly as given, and the frames are drawn around the
|
|
162
|
+
devices where they land.
|
|
163
|
+
|
|
164
|
+
Sketch first, build second, is the better order for anything a person will
|
|
165
|
+
look at: you can see the whole diagram before a single cable exists, and the
|
|
166
|
+
skill's job narrows to making the file match the drawing.
|
|
167
|
+
|
|
168
|
+
Without the knob the packing applies, and it is deterministic: the same
|
|
169
|
+
blueprint gives the same coordinates every time, so a regenerated lab does not
|
|
170
|
+
drift.
|
|
171
|
+
|
|
172
|
+
### Scale
|
|
173
|
+
|
|
174
|
+
There is no artificial device limit. The only ceiling is physical: a switch has
|
|
175
|
+
the ports it has, and the generator says so plainly when it runs out. Ask for
|
|
176
|
+
more switches, not fewer hosts.
|
|
177
|
+
|
|
178
|
+
### If you lost the thread part-way through an edit chain
|
|
179
|
+
|
|
180
|
+
Almost nothing here needs remembering. `--doctor`, `--explain-plan` and
|
|
181
|
+
`--parity-report` all recompute from the install, the donor registry and the
|
|
182
|
+
bridge, so re-running one costs time and nothing else. Losing the conversation
|
|
183
|
+
does not make the skill less certain about anything it reports.
|
|
184
|
+
|
|
185
|
+
One question has no such source. Part-way through
|
|
186
|
+
`--explain-plan` -> `--edit` -> `--parity-report`, *which lab am I working on
|
|
187
|
+
and what comes next* lived only in the conversation. So each run appends a line
|
|
188
|
+
to `output/session-log.jsonl`, and two flags read it back:
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
python scripts/generate_pkt.py --session-state # which labs are in flight
|
|
192
|
+
python scripts/generate_pkt.py --resume output/lab.pkt # where this one was left
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
`--resume` re-hashes the lab and compares it to what the last step recorded. It
|
|
196
|
+
reports a position **only when the two agree**; when they do not, it says the
|
|
197
|
+
lab has changed and tells you to re-derive instead. Treat "position not
|
|
198
|
+
claimed" as the correct answer, not a failure -- it is the same refusal-first
|
|
199
|
+
stance as everything else here.
|
|
200
|
+
|
|
201
|
+
Three things the log deliberately is not:
|
|
202
|
+
|
|
203
|
+
- **not a secret store.** Facts are allow-listed, never filtered. An edit
|
|
204
|
+
prompt carries a passphrase in an ordinary field, so nothing is written but
|
|
205
|
+
the named, known-safe keys, and the prompt survives only as the same
|
|
206
|
+
non-reversible shape fingerprint the usage ledger uses.
|
|
207
|
+
- **not load-bearing.** Delete it and every result is identical; there is a
|
|
208
|
+
test that regenerates a lab with and without it and compares the content.
|
|
209
|
+
- **not shipped.** It lives under `output/`, which is gitignored and in no
|
|
210
|
+
`package.json` file list.
|
|
211
|
+
|
|
212
|
+
`PKT_SESSION_LOG=off` turns it off; `PKT_SESSION_LOG=<path>` moves it.
|
|
213
|
+
|
|
214
|
+
### A generated lab becomes the next build's donor
|
|
215
|
+
|
|
216
|
+
Donor selection can pick a lab this skill produced, so every repair pass
|
|
217
|
+
eventually runs over its own output. Write interface configuration with
|
|
218
|
+
`_set_config_block`, never by appending, and check a new pass by running it
|
|
219
|
+
three times and asserting the document stops changing after the first.
|
|
220
|
+
|
|
221
|
+
### The palette is not the vocabulary
|
|
222
|
+
|
|
223
|
+
Packet Tracer's device palette lists more kinds than a saved lab distinguishes:
|
|
224
|
+
`SMARTPHONE-PT` saves as `Pda`, `Fiber Patch Panel` saves as `Patch Panel`.
|
|
225
|
+
Adding an askable kind with no donor behind it produces a request that can only
|
|
226
|
+
come back as an undelivered device. `tests/test_askable_kinds_have_donors.py`
|
|
227
|
+
holds the vocabulary to what real labs actually contain, in both directions.
|
|
228
|
+
|
|
229
|
+
### When generation refuses
|
|
230
|
+
|
|
231
|
+
A refusal returns `blocking_gaps` together with a `blueprint_plan`. That is not
|
|
232
|
+
a failure to work around by loosening a policy -- it is the skill saying no
|
|
233
|
+
donor can serve the request. Read the gaps, adjust the request or supply a
|
|
234
|
+
donor, and try again.
|
|
235
|
+
|
|
236
|
+
### What works without Packet Tracer installed
|
|
237
|
+
|
|
238
|
+
Decode, inventory, edit, generate and structural verification all work from the
|
|
239
|
+
donor cache with no install and no environment variables -- the Twofish engine
|
|
240
|
+
is vendored pure Python. Only `--validate-open` and live pings need the
|
|
241
|
+
application itself. Tests that build a lab are marked `requires_donors` and skip
|
|
242
|
+
where there is nothing to build from.
|
|
243
|
+
|
|
40
244
|
## How `.pkt` Files Work
|
|
41
245
|
|
|
42
246
|
For the modern format targeted by this skill, the pipeline is:
|
|
@@ -226,8 +430,10 @@ If the user does not specify details:
|
|
|
226
430
|
|
|
227
431
|
## Constraints
|
|
228
432
|
|
|
229
|
-
- The builder
|
|
230
|
-
|
|
433
|
+
- The builder needs Packet Tracer's bundled sample saves as prototype sources,
|
|
434
|
+
from a local installation on Windows, macOS or Linux **or** from the donor
|
|
435
|
+
cache under `~/.pkt/saves`, which a single generate run on an installed
|
|
436
|
+
machine populates and which can then be copied anywhere
|
|
231
437
|
- Donor devices the plan does not need are deleted. `PACKET_TRACER_SPARE_STRATEGY=park`
|
|
232
438
|
restores the older behaviour of renaming them `UNUSED-*` / `*-SPARE-*` and
|
|
233
439
|
moving them offscreen, if a donor turns out to depend on one staying present
|
|
@@ -252,6 +458,12 @@ Generate from a blueprint file:
|
|
|
252
458
|
python scripts/generate_pkt.py --blueprint examples/blueprint_minimal.json --output output\minimal.pkt
|
|
253
459
|
```
|
|
254
460
|
|
|
461
|
+
Check what a finished lab contradicts about itself, and exit non-zero if it does:
|
|
462
|
+
|
|
463
|
+
```powershell
|
|
464
|
+
python scripts/generate_pkt.py --coherence-report output\campus.pkt
|
|
465
|
+
```
|
|
466
|
+
|
|
255
467
|
Generate from a hybrid prompt:
|
|
256
468
|
|
|
257
469
|
```powershell
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "packet-tracer-skill",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Donor-backed Cisco Packet Tracer 9.x skill for natural-language planning, safe-open generation, editing, parity reporting, and runtime diagnostics across coding hosts.",
|
|
6
6
|
"keywords": [
|
|
@@ -75,6 +75,7 @@
|
|
|
75
75
|
"scripts/vendor/twofish_pure.py",
|
|
76
76
|
"templates",
|
|
77
77
|
"references/*.json",
|
|
78
|
+
"!references/local-donor-catalog.json",
|
|
78
79
|
"references/*.md",
|
|
79
80
|
"examples/README.md",
|
|
80
81
|
"examples/gallery.md",
|
|
@@ -11,19 +11,61 @@ now calls it rather than keeping a second, weaker copy.
|
|
|
11
11
|
|
|
12
12
|
from __future__ import annotations
|
|
13
13
|
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
14
16
|
from packet_tracer_env import require_packet_tracer_saves_root
|
|
15
17
|
from sample_catalog import _summarize_pkt, write_catalog_outputs
|
|
16
18
|
|
|
17
19
|
|
|
20
|
+
def _local_donor_items(catalogued: set[str]) -> list[dict]:
|
|
21
|
+
"""Labs the user saved outside the Packet Tracer installation.
|
|
22
|
+
|
|
23
|
+
Donor selection ranks candidates from this catalogue, and the catalogue
|
|
24
|
+
only ever held the installed samples. So a lab saved specifically to serve
|
|
25
|
+
as a donor was discovered by `discover_local_donors`, reported as present,
|
|
26
|
+
and never ranked -- generation for a five-switch topology stayed blocked
|
|
27
|
+
with "no ranked donor candidate passed validation" while a donor that fit
|
|
28
|
+
sat on disk.
|
|
29
|
+
|
|
30
|
+
These entries carry an absolute `path` rather than one relative to the
|
|
31
|
+
saves root, which is why `_catalog_item_path` has to keep it.
|
|
32
|
+
"""
|
|
33
|
+
try:
|
|
34
|
+
from local_donors import discover_local_donors
|
|
35
|
+
except ImportError: # pragma: no cover - discovery is optional
|
|
36
|
+
return []
|
|
37
|
+
|
|
38
|
+
items: list[dict] = []
|
|
39
|
+
for donor in discover_local_donors():
|
|
40
|
+
path = Path(donor.path)
|
|
41
|
+
if str(path) in catalogued or not path.is_file():
|
|
42
|
+
continue
|
|
43
|
+
try:
|
|
44
|
+
item = _summarize_pkt(path, path.name, "user-local", True)
|
|
45
|
+
except Exception as exc:
|
|
46
|
+
items.append(
|
|
47
|
+
{"relative_path": path.name, "source_path": str(path), "error": f"{type(exc).__name__}: {exc}"}
|
|
48
|
+
)
|
|
49
|
+
continue
|
|
50
|
+
# `source_path`, not `path`: the writer drops `path` so the committed
|
|
51
|
+
# catalogue does not depend on whose machine built it.
|
|
52
|
+
item["source_path"] = str(path)
|
|
53
|
+
items.append(item)
|
|
54
|
+
return items
|
|
55
|
+
|
|
56
|
+
|
|
18
57
|
def build_catalog() -> list[dict]:
|
|
19
58
|
saves_root = require_packet_tracer_saves_root()
|
|
20
59
|
items: list[dict] = []
|
|
60
|
+
catalogued: set[str] = set()
|
|
21
61
|
for path in sorted(saves_root.rglob("*.pkt")):
|
|
22
62
|
relative_path = str(path.relative_to(saves_root))
|
|
63
|
+
catalogued.add(str(path))
|
|
23
64
|
try:
|
|
24
65
|
items.append(_summarize_pkt(path, relative_path, "cisco-local", True))
|
|
25
66
|
except Exception as exc:
|
|
26
67
|
items.append({"relative_path": relative_path, "error": f"{type(exc).__name__}: {exc}"})
|
|
68
|
+
items.extend(_local_donor_items(catalogued))
|
|
27
69
|
return items
|
|
28
70
|
|
|
29
71
|
|