packet-tracer-skill 0.1.0 → 0.2.0

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.
Files changed (72) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/CITATION.cff +15 -0
  3. package/CODE_OF_CONDUCT.md +26 -0
  4. package/CONTRIBUTING.md +120 -0
  5. package/README.md +164 -543
  6. package/SECURITY.md +36 -0
  7. package/SKILL.md +46 -5
  8. package/bin/packet-tracer-skill.js +503 -7
  9. package/docs/curated-donor-registry.md +51 -0
  10. package/docs/discovery-keywords.md +55 -0
  11. package/docs/github-discussions-setup.md +34 -0
  12. package/docs/github-metadata.md +33 -0
  13. package/docs/hero-demo-plan.md +42 -0
  14. package/docs/publish-preview-roadmap.md +31 -0
  15. package/docs/release-checklist.md +60 -0
  16. package/docs/release-notes-0.2.0.md +25 -0
  17. package/docs/runtime-truth.md +49 -0
  18. package/docs/screenshots/.gitkeep +1 -0
  19. package/docs/screenshots/packet-tracer-topology-cropped.png +0 -0
  20. package/examples/README.md +72 -0
  21. package/examples/complex_campus_master_edit_v4.inventory.json +94 -0
  22. package/examples/gallery.md +39 -0
  23. package/examples/home_iot_cli_edit_v1.inventory.json +58 -0
  24. package/examples/index.json +127 -0
  25. package/examples/screenshots/complex_campus_master_edit_v4.png +0 -0
  26. package/examples/screenshots/home_iot_cli_edit_v1.png +0 -0
  27. package/examples/screenshots/service_heavy_cli_edit_v1.png +0 -0
  28. package/examples/screenshots/service_heavy_cli_edit_v1_dhcp.png +0 -0
  29. package/examples/screenshots/service_heavy_cli_edit_v1_dns.png +0 -0
  30. package/examples/screenshots/service_heavy_cli_edit_v1_ftp.png +0 -0
  31. package/examples/service_heavy_cli_edit_v1.inventory.json +51 -0
  32. package/package.json +41 -4
  33. package/references/curated-donor-registry.json +71 -0
  34. package/references/scenario-fixture-corpus.json +53 -0
  35. package/scripts/__pycache__/build_examples_index.cpython-314.pyc +0 -0
  36. package/scripts/__pycache__/build_sample_catalog.cpython-314.pyc +0 -0
  37. package/scripts/__pycache__/coverage_matrix.cpython-314.pyc +0 -0
  38. package/scripts/__pycache__/donor_diagnostics.cpython-314.pyc +0 -0
  39. package/scripts/__pycache__/generate_pkt.cpython-314.pyc +0 -0
  40. package/scripts/__pycache__/install_skill.cpython-314.pyc +0 -0
  41. package/scripts/__pycache__/intent_parser.cpython-314.pyc +0 -0
  42. package/scripts/__pycache__/packet_tracer_env.cpython-314.pyc +0 -0
  43. package/scripts/__pycache__/pkt_builder.cpython-314.pyc +0 -0
  44. package/scripts/__pycache__/pkt_codec.cpython-314.pyc +0 -0
  45. package/scripts/__pycache__/pkt_editor.cpython-314.pyc +0 -0
  46. package/scripts/__pycache__/pkt_transformer.cpython-314.pyc +0 -0
  47. package/scripts/__pycache__/remote_search.cpython-314.pyc +0 -0
  48. package/scripts/__pycache__/runtime_doctor.cpython-314.pyc +0 -0
  49. package/scripts/__pycache__/sample_catalog.cpython-314.pyc +0 -0
  50. package/scripts/__pycache__/sample_selector.cpython-314.pyc +0 -0
  51. package/scripts/__pycache__/twofish_diagnostics.cpython-314.pyc +0 -0
  52. package/scripts/__pycache__/twofish_runtime.cpython-314.pyc +0 -0
  53. package/scripts/__pycache__/workspace_repair.cpython-314.pyc +0 -0
  54. package/scripts/build_examples_index.py +314 -0
  55. package/scripts/coverage_matrix.py +783 -0
  56. package/scripts/donor_diagnostics.py +6 -3
  57. package/scripts/generate_pkt.py +2605 -98
  58. package/scripts/intent_parser.py +173 -18
  59. package/scripts/packet_tracer_env.py +127 -14
  60. package/scripts/pkt_editor.py +312 -12
  61. package/scripts/pkt_transformer.py +195 -9
  62. package/scripts/remote_search.py +169 -0
  63. package/scripts/runtime_doctor.py +286 -0
  64. package/scripts/sample_catalog.py +643 -38
  65. package/scripts/sample_selector.py +251 -5
  66. package/scripts/setup.ps1 +2 -1
  67. package/scripts/twofish_diagnostics.py +20 -23
  68. package/scripts/twofish_runtime.py +101 -0
  69. package/scripts/vendor/README.md +13 -1
  70. package/scripts/vendor/__pycache__/twofish.cpython-314.pyc +0 -0
  71. package/scripts/vendor/twofish.py +11 -9
  72. package/scripts/workspace_repair.py +69 -2
@@ -143,6 +143,7 @@ function verifyInstall(target) {
143
143
  path.join("scripts", "donor_diagnostics.py"),
144
144
  path.join("scripts", "install_skill.py"),
145
145
  path.join("scripts", "packet_tracer_env.py"),
146
+ path.join("scripts", "runtime_doctor.py"),
146
147
  path.join("scripts", "twofish_diagnostics.py"),
147
148
  ];
148
149
  const missing = required.filter((rel) => !fs.existsSync(path.join(target, rel)));
@@ -154,11 +155,30 @@ function verifyInstall(target) {
154
155
  }
155
156
 
156
157
  function commandExists(command) {
158
+ if (path.isAbsolute(command) || command.includes(path.sep)) {
159
+ return fs.existsSync(command);
160
+ }
157
161
  const probe = process.platform === "win32" ? "where" : "which";
158
162
  const result = spawnSync(probe, [command], { stdio: "ignore" });
159
163
  return result.status === 0;
160
164
  }
161
165
 
166
+ function preferredPythonCommand() {
167
+ const candidates = [
168
+ process.env.PACKET_TRACER_SKILL_PYTHON,
169
+ process.env.PYTHON,
170
+ process.platform === "win32" ? path.join("C:\\", "Python314", "python.exe") : "",
171
+ "python",
172
+ "py",
173
+ ].filter(Boolean);
174
+ for (const candidate of candidates) {
175
+ if (commandExists(candidate)) {
176
+ return candidate;
177
+ }
178
+ }
179
+ return null;
180
+ }
181
+
162
182
  function envPathStatus(value) {
163
183
  if (!value) {
164
184
  return { ok: false, message: "not set" };
@@ -170,7 +190,7 @@ function envPathStatus(value) {
170
190
  }
171
191
 
172
192
  function packetTracerVersionDiagnostics() {
173
- const pythonCommand = firstAvailableCommand(["python", "py"]);
193
+ const pythonCommand = preferredPythonCommand();
174
194
  if (!pythonCommand) {
175
195
  return {
176
196
  targetVersion: process.env.PACKET_TRACER_TARGET_VERSION || "9.0.0.0810",
@@ -267,12 +287,14 @@ function packetTracerVersionDiagnostics() {
267
287
  }
268
288
 
269
289
  function twofishDiagnostics() {
270
- const pythonCommand = firstAvailableCommand(["python", "py"]);
290
+ const pythonCommand = preferredPythonCommand();
271
291
  if (!pythonCommand) {
272
292
  return {
273
293
  pythonVersion: "",
274
294
  pythonSupportStatus: "missing",
275
295
  pythonSupportMessage: "python was not found in PATH",
296
+ expectedTwofishPatterns: [],
297
+ twofishSearchRoots: [],
276
298
  resolvedTwofishPath: "",
277
299
  twofishSource: "",
278
300
  twofishLoadStatus: "python_missing",
@@ -291,6 +313,8 @@ function twofishDiagnostics() {
291
313
  pythonVersion: "",
292
314
  pythonSupportStatus: "unknown",
293
315
  pythonSupportMessage: `inspection blocked in this host wrapper: ${result.error.message}`,
316
+ expectedTwofishPatterns: [],
317
+ twofishSearchRoots: [],
294
318
  resolvedTwofishPath: envPath,
295
319
  twofishSource: envPath ? "env" : "",
296
320
  twofishLoadStatus: envExists ? "inspection_blocked" : "missing",
@@ -310,6 +334,8 @@ function twofishDiagnostics() {
310
334
  pythonVersion: "",
311
335
  pythonSupportStatus: "unknown",
312
336
  pythonSupportMessage: detail,
337
+ expectedTwofishPatterns: [],
338
+ twofishSearchRoots: [],
313
339
  resolvedTwofishPath: process.env.PKT_TWOFISH_LIBRARY || "",
314
340
  twofishSource: process.env.PKT_TWOFISH_LIBRARY ? "env" : "",
315
341
  twofishLoadStatus: "load_error",
@@ -324,6 +350,8 @@ function twofishDiagnostics() {
324
350
  pythonVersion: parsed.python_version || "",
325
351
  pythonSupportStatus: parsed.python_support_status || "unknown",
326
352
  pythonSupportMessage: parsed.python_support_message || "unknown",
353
+ expectedTwofishPatterns: parsed.expected_twofish_patterns || [],
354
+ twofishSearchRoots: parsed.twofish_search_roots || [],
327
355
  resolvedTwofishPath: parsed.resolved_twofish_path || "",
328
356
  twofishSource: parsed.twofish_source || "",
329
357
  twofishLoadStatus: parsed.twofish_load_status || "unknown",
@@ -335,6 +363,8 @@ function twofishDiagnostics() {
335
363
  pythonVersion: "",
336
364
  pythonSupportStatus: "unknown",
337
365
  pythonSupportMessage: error.message,
366
+ expectedTwofishPatterns: [],
367
+ twofishSearchRoots: [],
338
368
  resolvedTwofishPath: process.env.PKT_TWOFISH_LIBRARY || "",
339
369
  twofishSource: process.env.PKT_TWOFISH_LIBRARY ? "env" : "",
340
370
  twofishLoadStatus: "load_error",
@@ -344,6 +374,124 @@ function twofishDiagnostics() {
344
374
  }
345
375
  }
346
376
 
377
+ function runtimeDoctorDiagnostics() {
378
+ const pythonCommand = preferredPythonCommand();
379
+ if (!pythonCommand) {
380
+ return {
381
+ available: false,
382
+ error: "python_missing",
383
+ hostOs: process.platform,
384
+ installerSupport: { status: "supported", message: "supported" },
385
+ realPktRuntimeSupport: {
386
+ status: "windows_first",
387
+ message: "python was not found in PATH",
388
+ },
389
+ envExamples: [],
390
+ detectedLayoutType: "missing",
391
+ recommendedPacketTracerRoot: "",
392
+ recommendedPacketTracerSavesRoot: "",
393
+ capabilityImpact: {},
394
+ runtimeBlockers: [],
395
+ readyOperations: [],
396
+ blockedOperations: [],
397
+ doctorSummary: "Runtime is blocked: python was not found in PATH.",
398
+ runtimeGrade: "blocked",
399
+ bridgeResolution: process.env.PKT_TWOFISH_LIBRARY ? "external_env" : "missing",
400
+ bridgePathSource: process.env.PKT_TWOFISH_LIBRARY ? "env" : "",
401
+ bridgeRecommendation: process.env.PKT_TWOFISH_LIBRARY
402
+ ? "Use or install a repo-local vendor bridge for fully self-contained runtime readiness."
403
+ : "Provide PKT_TWOFISH_LIBRARY or PKT_TWOFISH_SEARCH_ROOTS to resolve a local bridge.",
404
+ runtimeContractNotes: process.env.PKT_TWOFISH_LIBRARY
405
+ ? "External bridge resolves decode/edit, but repo-local runtime packaging is still incomplete."
406
+ : "Bridge resolution is missing, so strict runtime remains blocked.",
407
+ pythonVersion: "",
408
+ pythonSupportStatus: "missing",
409
+ pythonSupportMessage: "python was not found in PATH",
410
+ expectedTwofishPatterns: [],
411
+ twofishSearchRoots: [],
412
+ packetTracerRoot: process.env.PACKET_TRACER_ROOT || "",
413
+ packetTracerSavesRoot: "",
414
+ packetTracerExe: "",
415
+ resolvedTwofishPath: process.env.PKT_TWOFISH_LIBRARY || "",
416
+ twofishSource: process.env.PKT_TWOFISH_LIBRARY ? "env" : "",
417
+ twofishLoadStatus: "python_missing",
418
+ twofishMessage: "python was not found in PATH",
419
+ twofishSha256: "",
420
+ targetVersion: process.env.PACKET_TRACER_TARGET_VERSION || "9.0.0.0810",
421
+ resolvedDonorPath: process.env.PACKET_TRACER_COMPAT_DONOR || "",
422
+ donorVersion: "",
423
+ donorSource: process.env.PACKET_TRACER_COMPAT_DONOR ? "env" : "",
424
+ donorStatus: process.env.PACKET_TRACER_COMPAT_DONOR ? "unknown" : "missing",
425
+ donorMessage: process.env.PACKET_TRACER_COMPAT_DONOR ? "python missing" : "not set",
426
+ donorBlockingReason: "python was not found in PATH",
427
+ donorCandidates: [],
428
+ blockingReason: "python was not found in PATH",
429
+ };
430
+ }
431
+
432
+ const diagnosticsScript = path.join(REPO_ROOT, "scripts", "runtime_doctor.py");
433
+ const args = pythonCommand === "py" ? ["-3", diagnosticsScript] : [diagnosticsScript];
434
+ const result = runCaptured(pythonCommand, args);
435
+ if (result.error || result.status !== 0) {
436
+ return {
437
+ available: false,
438
+ error: result.error ? result.error.message : `exit code ${result.status}`,
439
+ };
440
+ }
441
+
442
+ try {
443
+ const parsed = JSON.parse((result.stdout || "").trim() || "{}");
444
+ return {
445
+ available: true,
446
+ hostOs: parsed.host_os || process.platform,
447
+ installerSupport: parsed.installer_support || { status: "unknown", message: "unknown" },
448
+ realPktRuntimeSupport: parsed.real_pkt_runtime_support || { status: "unknown", message: "unknown" },
449
+ envExamples: parsed.env_examples || [],
450
+ detectedLayoutType: parsed.detected_layout_type || "unknown",
451
+ recommendedPacketTracerRoot: parsed.recommended_packet_tracer_root || "",
452
+ recommendedPacketTracerSavesRoot: parsed.recommended_packet_tracer_saves_root || "",
453
+ recommendedNextSteps: parsed.recommended_next_steps || [],
454
+ capabilityImpact: parsed.capability_impact || {},
455
+ runtimeBlockers: parsed.runtime_blockers || [],
456
+ readyOperations: parsed.ready_operations || [],
457
+ blockedOperations: parsed.blocked_operations || [],
458
+ doctorSummary: parsed.doctor_summary || "",
459
+ runtimeGrade: parsed.runtime_grade || "blocked",
460
+ bridgeResolution: parsed.bridge_resolution || "missing",
461
+ bridgePathSource: parsed.bridge_path_source || "",
462
+ bridgeRecommendation: parsed.bridge_recommendation || "",
463
+ runtimeContractNotes: parsed.runtime_contract_notes || "",
464
+ packetTracerRoot: parsed.packet_tracer_root || process.env.PACKET_TRACER_ROOT || "",
465
+ packetTracerSavesRoot: parsed.packet_tracer_saves_root || "",
466
+ packetTracerExe: parsed.packet_tracer_exe || "",
467
+ pythonVersion: parsed.python_version || "",
468
+ pythonSupportStatus: parsed.python_support_status || "unknown",
469
+ pythonSupportMessage: parsed.python_support_message || "unknown",
470
+ expectedTwofishPatterns: parsed.expected_twofish_patterns || [],
471
+ twofishSearchRoots: parsed.twofish_search_roots || [],
472
+ resolvedTwofishPath: parsed.resolved_twofish_path || "",
473
+ twofishSource: parsed.twofish_source || "",
474
+ twofishLoadStatus: parsed.twofish_load_status || "unknown",
475
+ twofishMessage: parsed.twofish_message || "unknown",
476
+ twofishSha256: parsed.twofish_sha256 || "",
477
+ targetVersion: parsed.target_version || process.env.PACKET_TRACER_TARGET_VERSION || "9.0.0.0810",
478
+ resolvedDonorPath: parsed.resolved_donor_path || process.env.PACKET_TRACER_COMPAT_DONOR || "",
479
+ donorVersion: parsed.donor_version || "",
480
+ donorSource: parsed.donor_source || "",
481
+ donorStatus: parsed.donor_status || "unknown",
482
+ donorMessage: parsed.donor_message || "unknown",
483
+ donorBlockingReason: parsed.donor_blocking_reason || "",
484
+ donorCandidates: parsed.donor_candidates || [],
485
+ blockingReason: parsed.blocking_reason || "",
486
+ };
487
+ } catch (error) {
488
+ return {
489
+ available: false,
490
+ error: error.message,
491
+ };
492
+ }
493
+ }
494
+
347
495
  function requirementLines(requirementsPath) {
348
496
  if (!fs.existsSync(requirementsPath)) {
349
497
  return [];
@@ -385,7 +533,7 @@ function firstAvailableCommand(candidates) {
385
533
  }
386
534
 
387
535
  function bootstrapEnvironment(target, includeDev) {
388
- const pythonCommand = firstAvailableCommand(["python", "py"]);
536
+ const pythonCommand = preferredPythonCommand();
389
537
  if (!pythonCommand) {
390
538
  return {
391
539
  ok: false,
@@ -453,13 +601,104 @@ function doctorChecks() {
453
601
  ["node", nodeOk, nodeOk ? "found" : "missing"],
454
602
  ["python", pythonOk, pythonOk ? "found" : "missing"],
455
603
  ];
456
-
457
- const root = envPathStatus(process.env.PACKET_TRACER_ROOT);
458
- const donorDiagnostics = packetTracerVersionDiagnostics();
459
- const twofish = twofishDiagnostics();
604
+ const runtimeDoctor = runtimeDoctorDiagnostics();
605
+ const donorDiagnostics = runtimeDoctor.available
606
+ ? {
607
+ targetVersion: runtimeDoctor.targetVersion,
608
+ donorPath: runtimeDoctor.resolvedDonorPath,
609
+ donorVersion: runtimeDoctor.donorVersion,
610
+ donorSource: runtimeDoctor.donorSource,
611
+ status: runtimeDoctor.donorStatus,
612
+ message: runtimeDoctor.donorMessage,
613
+ blockingReason: runtimeDoctor.donorBlockingReason,
614
+ candidatePaths: runtimeDoctor.donorCandidates,
615
+ }
616
+ : packetTracerVersionDiagnostics();
617
+ const twofish = runtimeDoctor.available
618
+ ? {
619
+ pythonVersion: runtimeDoctor.pythonVersion,
620
+ pythonSupportStatus: runtimeDoctor.pythonSupportStatus,
621
+ pythonSupportMessage: runtimeDoctor.pythonSupportMessage,
622
+ expectedTwofishPatterns: runtimeDoctor.expectedTwofishPatterns,
623
+ twofishSearchRoots: runtimeDoctor.twofishSearchRoots,
624
+ resolvedTwofishPath: runtimeDoctor.resolvedTwofishPath,
625
+ twofishSource: runtimeDoctor.twofishSource,
626
+ twofishLoadStatus: runtimeDoctor.twofishLoadStatus,
627
+ twofishMessage: runtimeDoctor.twofishMessage,
628
+ twofishSha256: runtimeDoctor.twofishSha256,
629
+ }
630
+ : twofishDiagnostics();
631
+ const hostOs = runtimeDoctor.available ? runtimeDoctor.hostOs : process.platform;
632
+ const detectedLayoutType = runtimeDoctor.available ? runtimeDoctor.detectedLayoutType : "fallback_unknown";
633
+ const recommendedPacketTracerRoot = runtimeDoctor.available
634
+ ? runtimeDoctor.recommendedPacketTracerRoot
635
+ : fallbackRecommendedPacketTracerRoot(process.platform);
636
+ const recommendedPacketTracerSavesRoot = runtimeDoctor.available
637
+ ? runtimeDoctor.recommendedPacketTracerSavesRoot
638
+ : fallbackRecommendedPacketTracerSavesRoot(process.platform);
639
+ const installerSupported = runtimeDoctor.available
640
+ ? runtimeDoctor.installerSupport.status === "supported"
641
+ : ["win32", "darwin", "linux"].includes(process.platform);
642
+ const runtimeSupported = runtimeDoctor.available
643
+ ? runtimeDoctor.realPktRuntimeSupport.status === "validated"
644
+ : process.platform === "win32";
645
+ const runtimeMessage = runtimeDoctor.available
646
+ ? runtimeDoctor.realPktRuntimeSupport.message
647
+ : runtimeSupported
648
+ ? "validated Windows Packet Tracer 9.0 runtime path"
649
+ : twofish.resolvedTwofishPath
650
+ ? "custom native runtime may work, but bundled validation is still Windows-first"
651
+ : "needs custom Packet Tracer paths and a non-Windows native Twofish bridge";
652
+ const packetTracerRoot = runtimeDoctor.available
653
+ ? runtimeDoctor.packetTracerRoot
654
+ : process.env.PACKET_TRACER_ROOT || "";
655
+ const root = runtimeDoctor.available
656
+ ? {
657
+ ok: packetTracerRoot !== "",
658
+ message: packetTracerRoot || "not set",
659
+ }
660
+ : envPathStatus(process.env.PACKET_TRACER_ROOT);
460
661
 
461
662
  const strictTargetVersion = "9.0.0.0810";
663
+ checks.push(["HOST_OS", true, hostOs]);
664
+ checks.push([
665
+ "INSTALLER_SUPPORT",
666
+ installerSupported,
667
+ runtimeDoctor.available ? runtimeDoctor.installerSupport.message : installerSupported ? "supported" : "unknown host platform",
668
+ ]);
669
+ checks.push([
670
+ "DETECTED_LAYOUT_TYPE",
671
+ true,
672
+ detectedLayoutType || "unknown",
673
+ ]);
674
+ checks.push([
675
+ "RECOMMENDED_PACKET_TRACER_ROOT",
676
+ recommendedPacketTracerRoot !== "",
677
+ recommendedPacketTracerRoot || "not available",
678
+ ]);
679
+ checks.push([
680
+ "RECOMMENDED_PACKET_TRACER_SAVES_ROOT",
681
+ recommendedPacketTracerSavesRoot !== "",
682
+ recommendedPacketTracerSavesRoot || "not available",
683
+ ]);
684
+ checks.push([
685
+ "REAL_PKT_RUNTIME_SUPPORT",
686
+ runtimeSupported,
687
+ runtimeMessage,
688
+ ]);
462
689
  checks.push(["PACKET_TRACER_ROOT", root.ok, root.message]);
690
+ if (runtimeDoctor.available) {
691
+ checks.push([
692
+ "PACKET_TRACER_SAVES_ROOT",
693
+ runtimeDoctor.packetTracerSavesRoot !== "",
694
+ runtimeDoctor.packetTracerSavesRoot || "not resolved",
695
+ ]);
696
+ checks.push([
697
+ "PACKET_TRACER_EXE",
698
+ runtimeDoctor.packetTracerExe !== "",
699
+ runtimeDoctor.packetTracerExe || "not resolved",
700
+ ]);
701
+ }
463
702
  checks.push([
464
703
  "PYTHON_VERSION",
465
704
  twofish.pythonVersion !== "",
@@ -528,6 +767,124 @@ function doctorChecks() {
528
767
  twofish.twofishSha256 !== "",
529
768
  twofish.twofishSha256 || "not available",
530
769
  ]);
770
+ checks.push([
771
+ "TWOFISH_EXPECTED_PATTERNS",
772
+ (twofish.expectedTwofishPatterns || []).length > 0,
773
+ (twofish.expectedTwofishPatterns || []).join(" | ") || "not available",
774
+ ]);
775
+ checks.push([
776
+ "TWOFISH_SEARCH_ROOTS",
777
+ (twofish.twofishSearchRoots || []).length > 0,
778
+ (twofish.twofishSearchRoots || []).join(" | ") || "not available",
779
+ ]);
780
+ const runtimeBlockers = runtimeDoctor.available
781
+ ? runtimeDoctor.runtimeBlockers || []
782
+ : [
783
+ donorDiagnostics.status === "ok" ? "" : "missing_or_incompatible_donor",
784
+ twofish.twofishLoadStatus === "ok" ? "" : "missing_twofish_bridge",
785
+ packetTracerRoot ? "" : "missing_packet_tracer_root",
786
+ process.platform === "win32" ? "" : "windows_first_runtime",
787
+ ].filter(Boolean);
788
+ const readyOperations = runtimeDoctor.available
789
+ ? runtimeDoctor.readyOperations || []
790
+ : [
791
+ donorDiagnostics.status === "ok" && twofish.twofishLoadStatus === "ok" ? "decode" : "",
792
+ donorDiagnostics.status === "ok" && twofish.twofishLoadStatus === "ok" ? "edit" : "",
793
+ donorDiagnostics.status === "ok" &&
794
+ twofish.twofishLoadStatus === "ok" &&
795
+ packetTracerRoot
796
+ ? "generate"
797
+ : "",
798
+ ].filter(Boolean);
799
+ const capabilityImpact = runtimeDoctor.available
800
+ ? runtimeDoctor.capabilityImpact || {}
801
+ : {
802
+ inventory: donorDiagnostics.status === "ok" ? "ready" : "blocked",
803
+ decode: donorDiagnostics.status === "ok" && twofish.twofishLoadStatus === "ok" ? "ready" : "blocked",
804
+ edit: donorDiagnostics.status === "ok" && twofish.twofishLoadStatus === "ok" ? "ready" : "blocked",
805
+ generate:
806
+ donorDiagnostics.status === "ok" && twofish.twofishLoadStatus === "ok" && packetTracerRoot
807
+ ? "ready"
808
+ : "blocked",
809
+ validate_open: packetTracerRoot ? "ready" : "blocked",
810
+ };
811
+ const blockedOperations = runtimeDoctor.available
812
+ ? runtimeDoctor.blockedOperations || []
813
+ : Object.entries(capabilityImpact)
814
+ .filter(([, status]) => status !== "ready")
815
+ .map(([name]) => name);
816
+ const runtimeGrade = runtimeDoctor.available
817
+ ? runtimeDoctor.runtimeGrade || "blocked"
818
+ : runtimeBlockers.length === 0
819
+ ? "ready"
820
+ : readyOperations.length > 0
821
+ ? "partially_ready"
822
+ : "blocked";
823
+ const doctorSummary = runtimeDoctor.available
824
+ ? runtimeDoctor.doctorSummary || ""
825
+ : runtimeGrade === "ready"
826
+ ? "Runtime looks ready for decode, edit, generate, and validate-open."
827
+ : runtimeGrade === "partially_ready"
828
+ ? "Runtime is partially ready: some operations work, but strict .pkt generation is still blocked."
829
+ : "Runtime is blocked: required donor, bridge, or Packet Tracer runtime pieces are missing.";
830
+ const bridgeResolution = runtimeDoctor.available ? runtimeDoctor.bridgeResolution || "missing" : (process.env.PKT_TWOFISH_LIBRARY ? "external_env" : "missing");
831
+ const bridgeRecommendation = runtimeDoctor.available ? runtimeDoctor.bridgeRecommendation || "" : (process.env.PKT_TWOFISH_LIBRARY
832
+ ? "Use or install a repo-local vendor bridge for fully self-contained runtime readiness."
833
+ : "Provide PKT_TWOFISH_LIBRARY or PKT_TWOFISH_SEARCH_ROOTS to resolve a local bridge.");
834
+ const runtimeContractNotes = runtimeDoctor.available ? runtimeDoctor.runtimeContractNotes || "" : (process.env.PKT_TWOFISH_LIBRARY
835
+ ? "External bridge resolves decode/edit, but repo-local runtime packaging is still incomplete."
836
+ : "Bridge resolution is missing, so strict runtime remains blocked.");
837
+ const runtimeBlockerSummary = runtimeBlockers.join(" | ");
838
+ const readyOperationSummary = readyOperations.join(" | ");
839
+ const blockedOperationSummary = blockedOperations.join(" | ");
840
+ const capabilityImpactSummary = Object.entries(capabilityImpact)
841
+ .map(([name, status]) => `${name}=${status}`)
842
+ .join(" | ");
843
+ checks.push([
844
+ "RUNTIME_BLOCKERS",
845
+ runtimeBlockers.length === 0,
846
+ runtimeBlockerSummary || "none",
847
+ ]);
848
+ checks.push([
849
+ "READY_OPERATIONS",
850
+ readyOperations.length > 0,
851
+ readyOperationSummary || "none",
852
+ ]);
853
+ checks.push([
854
+ "BLOCKED_OPERATIONS",
855
+ blockedOperations.length === 0,
856
+ blockedOperationSummary || "none",
857
+ ]);
858
+ checks.push([
859
+ "RUNTIME_GRADE",
860
+ runtimeGrade === "ready",
861
+ runtimeGrade,
862
+ ]);
863
+ checks.push([
864
+ "DOCTOR_SUMMARY",
865
+ doctorSummary !== "",
866
+ doctorSummary || "not available",
867
+ ]);
868
+ checks.push([
869
+ "BRIDGE_RESOLUTION",
870
+ bridgeResolution !== "missing",
871
+ bridgeResolution,
872
+ ]);
873
+ checks.push([
874
+ "BRIDGE_RECOMMENDATION",
875
+ bridgeRecommendation !== "",
876
+ bridgeRecommendation || "not available",
877
+ ]);
878
+ checks.push([
879
+ "RUNTIME_CONTRACT_NOTES",
880
+ runtimeContractNotes !== "",
881
+ runtimeContractNotes || "not available",
882
+ ]);
883
+ checks.push([
884
+ "CAPABILITY_IMPACT",
885
+ capabilityImpactSummary !== "",
886
+ capabilityImpactSummary || "not available",
887
+ ]);
531
888
 
532
889
  return checks;
533
890
  }
@@ -538,6 +895,127 @@ function printDoctorChecks(checks) {
538
895
  }
539
896
  }
540
897
 
898
+ function checkMap(checks) {
899
+ return new Map(checks.map(([name, ok, detail]) => [name, { ok, detail }]));
900
+ }
901
+
902
+ function doctorGuidance(checks) {
903
+ const map = checkMap(checks);
904
+ const guidance = [];
905
+ const pythonSupport = map.get("PYTHON_SUPPORT_STATUS");
906
+ const root = map.get("PACKET_TRACER_ROOT");
907
+ const donor = map.get("PACKET_TRACER_COMPAT_DONOR");
908
+ const donorCandidates = map.get("DONOR_CANDIDATES");
909
+ const savesRoot = map.get("RECOMMENDED_PACKET_TRACER_SAVES_ROOT");
910
+ const twofish = map.get("TWOFISH_LOAD_STATUS");
911
+ const patterns = map.get("TWOFISH_EXPECTED_PATTERNS");
912
+ const searchRoots = map.get("TWOFISH_SEARCH_ROOTS");
913
+ const runtimeSupport = map.get("REAL_PKT_RUNTIME_SUPPORT");
914
+
915
+ if (runtimeSupport && !runtimeSupport.ok) {
916
+ guidance.push(`Real runtime is still Windows-first: ${runtimeSupport.detail}`);
917
+ }
918
+ if (pythonSupport && !pythonSupport.ok) {
919
+ guidance.push("Use Python 3.14.x for Packet Tracer 9.0 encode/decode.");
920
+ }
921
+ if (root && !root.ok) {
922
+ const suffix =
923
+ map.get("RECOMMENDED_PACKET_TRACER_ROOT")?.detail &&
924
+ map.get("RECOMMENDED_PACKET_TRACER_ROOT")?.detail !== "not available"
925
+ ? ` Suggested root: ${map.get("RECOMMENDED_PACKET_TRACER_ROOT").detail}.`
926
+ : "";
927
+ guidance.push(`Set PACKET_TRACER_ROOT to your local Cisco Packet Tracer 9.0 install directory.${suffix}`);
928
+ }
929
+ if (donor && !donor.ok) {
930
+ const savesHint =
931
+ savesRoot && savesRoot.detail && savesRoot.detail !== "not available"
932
+ ? ` Packet Tracer saves are typically under ${savesRoot.detail}.`
933
+ : "";
934
+ if (donorCandidates && donorCandidates.ok) {
935
+ guidance.push(`Pick one discovered donor or export PACKET_TRACER_COMPAT_DONOR explicitly: ${donorCandidates.detail}.${savesHint}`);
936
+ } else {
937
+ guidance.push(`Provide a working 9.0 donor lab with PACKET_TRACER_COMPAT_DONOR or place one in Downloads/Documents/Desktop/Packet Tracer saves.${savesHint}`);
938
+ }
939
+ }
940
+ if (twofish && !twofish.ok) {
941
+ const parts = ["Provide a local Twofish bridge"];
942
+ if (patterns && patterns.detail && patterns.detail !== "not available") {
943
+ parts.push(`matching one of: ${patterns.detail}`);
944
+ }
945
+ if (searchRoots && searchRoots.detail && searchRoots.detail !== "not available") {
946
+ parts.push(`in one of these roots: ${searchRoots.detail}`);
947
+ }
948
+ parts.push("or set PKT_TWOFISH_LIBRARY / PKT_TWOFISH_SEARCH_ROOTS.");
949
+ guidance.push(parts.join(" "));
950
+ }
951
+ return guidance;
952
+ }
953
+
954
+ function fallbackEnvExamples(platformName) {
955
+ if (platformName === "win32") {
956
+ return [
957
+ "$env:PACKET_TRACER_ROOT='C:\\Program Files\\Cisco Packet Tracer 9.0.0'",
958
+ "$env:PACKET_TRACER_COMPAT_DONOR='C:\\path\\to\\your-working-9.0-donor.pkt'",
959
+ '$env:PKT_TWOFISH_LIBRARY="$env:USERPROFILE\\.codex\\skills\\pkt\\scripts\\vendor\\_twofish.cp314-win_amd64.pyd"',
960
+ ];
961
+ }
962
+ if (platformName === "darwin") {
963
+ return [
964
+ "export PACKET_TRACER_ROOT='/Applications/Cisco Packet Tracer.app/Contents/Resources'",
965
+ "export PACKET_TRACER_COMPAT_DONOR=\"$HOME/path/to/your-working-9.0-donor.pkt\"",
966
+ "export PKT_TWOFISH_SEARCH_ROOTS=\"$HOME/.codex/skills/pkt/scripts/vendor:$HOME/pkt-bridges\"",
967
+ ];
968
+ }
969
+ if (platformName === "linux") {
970
+ return [
971
+ "export PACKET_TRACER_ROOT='/opt/pt'",
972
+ "export PACKET_TRACER_COMPAT_DONOR=\"$HOME/path/to/your-working-9.0-donor.pkt\"",
973
+ "export PKT_TWOFISH_SEARCH_ROOTS=\"$HOME/.codex/skills/pkt/scripts/vendor:$HOME/pkt-bridges\"",
974
+ ];
975
+ }
976
+ return [];
977
+ }
978
+
979
+ function fallbackRecommendedPacketTracerRoot(platformName) {
980
+ if (platformName === "win32") {
981
+ return "C:\\Program Files\\Cisco Packet Tracer 9.0.0";
982
+ }
983
+ if (platformName === "darwin") {
984
+ return "/Applications/Cisco Packet Tracer.app/Contents/Resources";
985
+ }
986
+ if (platformName === "linux") {
987
+ return "/opt/pt";
988
+ }
989
+ return "";
990
+ }
991
+
992
+ function fallbackRecommendedPacketTracerSavesRoot(platformName) {
993
+ if (platformName === "win32") {
994
+ return "C:\\Program Files\\Cisco Packet Tracer 9.0.0\\saves";
995
+ }
996
+ if (platformName === "darwin") {
997
+ return "/Applications/Cisco Packet Tracer.app/Contents/Resources/saves";
998
+ }
999
+ if (platformName === "linux") {
1000
+ return "/opt/pt/saves";
1001
+ }
1002
+ return "";
1003
+ }
1004
+
1005
+ function printEnvExamples(runtimeDoctor) {
1006
+ const examples =
1007
+ runtimeDoctor && runtimeDoctor.available && (runtimeDoctor.envExamples || []).length
1008
+ ? runtimeDoctor.envExamples
1009
+ : fallbackEnvExamples(process.platform);
1010
+ if (!examples.length) {
1011
+ return;
1012
+ }
1013
+ console.log("\nSuggested environment setup:");
1014
+ for (const line of examples) {
1015
+ console.log(`- ${line}`);
1016
+ }
1017
+ }
1018
+
541
1019
  function main() {
542
1020
  try {
543
1021
  const args = parseArgs(process.argv.slice(2));
@@ -547,8 +1025,17 @@ function main() {
547
1025
  }
548
1026
 
549
1027
  if (args.doctor) {
1028
+ const runtimeDoctor = runtimeDoctorDiagnostics();
550
1029
  const checks = doctorChecks();
551
1030
  printDoctorChecks(checks);
1031
+ const guidance = doctorGuidance(checks);
1032
+ if (guidance.length > 0) {
1033
+ console.log("\nRecommended next steps:");
1034
+ for (const line of guidance) {
1035
+ console.log(`- ${line}`);
1036
+ }
1037
+ }
1038
+ printEnvExamples(runtimeDoctor);
552
1039
  const failed = checks.some(([, ok]) => !ok);
553
1040
  if (failed) {
554
1041
  console.error("\nRuntime is not fully ready. Install copies are fine, but Packet Tracer generation still needs the missing items above.");
@@ -610,8 +1097,17 @@ function main() {
610
1097
  }
611
1098
 
612
1099
  const checks = doctorChecks();
1100
+ const runtimeDoctor = runtimeDoctorDiagnostics();
613
1101
  console.log("\nRemaining manual runtime checks:");
614
1102
  printDoctorChecks(checks);
1103
+ const guidance = doctorGuidance(checks);
1104
+ if (guidance.length > 0) {
1105
+ console.log("\nRecommended next steps:");
1106
+ for (const line of guidance) {
1107
+ console.log(`- ${line}`);
1108
+ }
1109
+ }
1110
+ printEnvExamples(runtimeDoctor);
615
1111
  const missing = checks.filter(([, ok]) => !ok).map(([name]) => name);
616
1112
  const donorCandidates = checks.find(([name]) => name === "DONOR_CANDIDATES");
617
1113
  if (donorCandidates && donorCandidates[2] && donorCandidates[2] !== "none discovered") {
@@ -0,0 +1,51 @@
1
+ # Curated Donor Registry
2
+
3
+ ## Purpose
4
+
5
+ The curated donor registry is the explicit truth source for donor promotion metadata that should not be guessed from filenames alone.
6
+
7
+ Registry file:
8
+
9
+ - `references/curated-donor-registry.json`
10
+
11
+ ## Entry Contract
12
+
13
+ Each entry should define:
14
+
15
+ - `relative_path`
16
+ - `promotion_status`
17
+ - `packet_tracer_version`
18
+ - `workspace_validation`
19
+ - `apply_safety_level`
20
+ - `archetype_tags`
21
+ - `validated_edit_capabilities`
22
+ - `acceptance_fixtures`
23
+ - `acceptance_notes`
24
+ - `provenance`
25
+
26
+ ## Current Seeded Entries
27
+
28
+ The current seeded entries are based on known working example artifacts:
29
+
30
+ - `complex_campus_master_edit_v4.pkt`
31
+ - `home_iot_cli_edit_v1.pkt`
32
+ - `service_heavy_cli_edit_v1.pkt`
33
+
34
+ These entries become active when a donor root contains matching relative paths or filenames.
35
+
36
+ ## Promotion Rules
37
+
38
+ - `reference_only` never becomes the final selected donor
39
+ - `acceptance_verified_curated` requires explicit `acceptance_fixtures`
40
+ - registry-backed metadata overrides inferred metadata where the registry is more explicit
41
+ - validation can still demote a registry entry if the actual donor is blocked or incompatible
42
+
43
+ ## Evidence Sources
44
+
45
+ Selected donor summaries distinguish:
46
+
47
+ - registry-backed evidence
48
+ - inferred evidence
49
+ - mixed `registry+inferred` evidence
50
+
51
+ This is important for auditability and release messaging.