aero-agent-roles 1.0.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 (170) hide show
  1. package/NOTICE +18 -0
  2. package/README.md +49 -0
  3. package/bin/aero-agent-roles.js +127 -0
  4. package/lib/catalog.js +97 -0
  5. package/lib/install.js +48 -0
  6. package/lib/mcp.js +199 -0
  7. package/manifest.json +516 -0
  8. package/package.json +44 -0
  9. package/roles/aerodynamics-engineer/ROLE.md +153 -0
  10. package/roles/aerodynamics-engineer/SOURCES.md +12 -0
  11. package/roles/aerodynamics-engineer/cli.py +143 -0
  12. package/roles/aerodynamics-engineer/core/aerodynamics_core.py +1218 -0
  13. package/roles/aerodynamics-engineer/templates/aero-design-report-template.md +72 -0
  14. package/roles/aerodynamics-engineer/tests/test_aerodynamics_core.py +227 -0
  15. package/roles/aerodynamics-engineer/tests/test_role_aerodynamics.py +94 -0
  16. package/roles/aircraft-design-engineer/ROLE.md +138 -0
  17. package/roles/aircraft-design-engineer/SOURCES.md +12 -0
  18. package/roles/aircraft-design-engineer/cli.py +152 -0
  19. package/roles/aircraft-design-engineer/core/aircraft_design_core.py +1278 -0
  20. package/roles/aircraft-design-engineer/templates/concept-design-template.md +99 -0
  21. package/roles/aircraft-design-engineer/tests/test_aircraft_design_core.py +269 -0
  22. package/roles/aircraft-design-engineer/tests/test_role_aircraft_design_engineer.py +106 -0
  23. package/roles/airworthiness-compliance-engineer/ROLE.md +122 -0
  24. package/roles/airworthiness-compliance-engineer/SOURCES.md +16 -0
  25. package/roles/airworthiness-compliance-engineer/cli.py +146 -0
  26. package/roles/airworthiness-compliance-engineer/core/airworthiness_core.py +894 -0
  27. package/roles/airworthiness-compliance-engineer/templates/compliance-matrix-template.md +46 -0
  28. package/roles/airworthiness-compliance-engineer/tests/test_airworthiness_core.py +224 -0
  29. package/roles/airworthiness-compliance-engineer/tests/test_role_airworthiness.py +85 -0
  30. package/roles/as9100-quality-auditor/ROLE.md +128 -0
  31. package/roles/as9100-quality-auditor/SOURCES.md +16 -0
  32. package/roles/as9100-quality-auditor/cli.py +136 -0
  33. package/roles/as9100-quality-auditor/core/as9100_core.py +857 -0
  34. package/roles/as9100-quality-auditor/templates/findings-report-template.md +119 -0
  35. package/roles/as9100-quality-auditor/tests/test_as9100_cli_profile.py +105 -0
  36. package/roles/as9100-quality-auditor/tests/test_as9100_core.py +204 -0
  37. package/roles/as9100-quality-auditor/tests/test_role_as9100.py +90 -0
  38. package/roles/data-bus-avionics-engineer/ROLE.md +146 -0
  39. package/roles/data-bus-avionics-engineer/SOURCES.md +23 -0
  40. package/roles/data-bus-avionics-engineer/cli.py +311 -0
  41. package/roles/data-bus-avionics-engineer/core/data_bus_avionics_core.py +1121 -0
  42. package/roles/data-bus-avionics-engineer/templates/bus-assessment-template.md +134 -0
  43. package/roles/data-bus-avionics-engineer/tests/test_bundle_protocol.py +137 -0
  44. package/roles/data-bus-avionics-engineer/tests/test_data_bus_avionics_core.py +261 -0
  45. package/roles/data-bus-avionics-engineer/tests/test_role_data_bus_avionics_engineer.py +121 -0
  46. package/roles/do160-environmental-engineer/ROLE.md +136 -0
  47. package/roles/do160-environmental-engineer/SOURCES.md +30 -0
  48. package/roles/do160-environmental-engineer/cli.py +147 -0
  49. package/roles/do160-environmental-engineer/core/do160_environmental_core.py +1246 -0
  50. package/roles/do160-environmental-engineer/templates/environmental-qualification-plan-template.md +114 -0
  51. package/roles/do160-environmental-engineer/tests/test_do160_environmental_core.py +312 -0
  52. package/roles/do160-environmental-engineer/tests/test_role_do160_environmental_engineer.py +281 -0
  53. package/roles/do178c-cert-engineer/ROLE.md +120 -0
  54. package/roles/do178c-cert-engineer/SOURCES.md +16 -0
  55. package/roles/do178c-cert-engineer/cli.py +129 -0
  56. package/roles/do178c-cert-engineer/core/do178c_core.py +411 -0
  57. package/roles/do178c-cert-engineer/templates/psac-template.md +74 -0
  58. package/roles/do178c-cert-engineer/tests/test_do178c_cert_engineer_core.py +91 -0
  59. package/roles/do178c-cert-engineer/tests/test_role_do178c.py +109 -0
  60. package/roles/do254-hardware-engineer/ROLE.md +127 -0
  61. package/roles/do254-hardware-engineer/SOURCES.md +19 -0
  62. package/roles/do254-hardware-engineer/cli.py +308 -0
  63. package/roles/do254-hardware-engineer/core/do254_hardware_core.py +761 -0
  64. package/roles/do254-hardware-engineer/templates/phac-template.md +87 -0
  65. package/roles/do254-hardware-engineer/tests/test_do254_bundle_profile.py +152 -0
  66. package/roles/do254-hardware-engineer/tests/test_do254_hardware_core.py +200 -0
  67. package/roles/do254-hardware-engineer/tests/test_role_do254_hardware.py +108 -0
  68. package/roles/engineering-analysis-engineer/ROLE.md +154 -0
  69. package/roles/engineering-analysis-engineer/SOURCES.md +15 -0
  70. package/roles/engineering-analysis-engineer/cli.py +151 -0
  71. package/roles/engineering-analysis-engineer/core/engineering_analysis_engineer_core.py +866 -0
  72. package/roles/engineering-analysis-engineer/templates/analysis-memo-template.md +73 -0
  73. package/roles/engineering-analysis-engineer/tests/test_engineering_analysis_engineer_core.py +367 -0
  74. package/roles/engineering-analysis-engineer/tests/test_role_engineering_analysis_engineer.py +113 -0
  75. package/roles/flight-management-engineer/ROLE.md +160 -0
  76. package/roles/flight-management-engineer/SOURCES.md +30 -0
  77. package/roles/flight-management-engineer/cli.py +258 -0
  78. package/roles/flight-management-engineer/core/flight_management_core.py +1473 -0
  79. package/roles/flight-management-engineer/templates/route-assessment-template.md +94 -0
  80. package/roles/flight-management-engineer/tests/test_flight_management_core.py +437 -0
  81. package/roles/flight-management-engineer/tests/test_role_flight_management_engineer.py +123 -0
  82. package/roles/flight-mechanics-engineer/ROLE.md +162 -0
  83. package/roles/flight-mechanics-engineer/SOURCES.md +51 -0
  84. package/roles/flight-mechanics-engineer/cli.py +149 -0
  85. package/roles/flight-mechanics-engineer/core/flight_mechanics_core.py +1507 -0
  86. package/roles/flight-mechanics-engineer/templates/perf-sc-report-template.md +82 -0
  87. package/roles/flight-mechanics-engineer/tests/test_flight_mechanics_core.py +277 -0
  88. package/roles/flight-mechanics-engineer/tests/test_role_flight_mechanics_engineer.py +141 -0
  89. package/roles/flight-test-engineer/ROLE.md +137 -0
  90. package/roles/flight-test-engineer/SOURCES.md +24 -0
  91. package/roles/flight-test-engineer/cli.py +135 -0
  92. package/roles/flight-test-engineer/core/flight_test_core.py +931 -0
  93. package/roles/flight-test-engineer/templates/flight-test-template.md +143 -0
  94. package/roles/flight-test-engineer/tests/test_flight_test_engineer_core.py +227 -0
  95. package/roles/flight-test-engineer/tests/test_role_flight_test_engineer.py +150 -0
  96. package/roles/flight-test-performance-engineer/ROLE.md +165 -0
  97. package/roles/flight-test-performance-engineer/SOURCES.md +20 -0
  98. package/roles/flight-test-performance-engineer/cli.py +319 -0
  99. package/roles/flight-test-performance-engineer/core/flight_test_performance_core.py +1398 -0
  100. package/roles/flight-test-performance-engineer/templates/performance-report-template.md +105 -0
  101. package/roles/flight-test-performance-engineer/tests/test_flight_test_performance_engineer_core.py +308 -0
  102. package/roles/flight-test-performance-engineer/tests/test_role_flight_test_performance_engineer.py +165 -0
  103. package/roles/gnc-engineer/ROLE.md +141 -0
  104. package/roles/gnc-engineer/SOURCES.md +35 -0
  105. package/roles/gnc-engineer/cli.py +141 -0
  106. package/roles/gnc-engineer/core/gnc_core.py +1074 -0
  107. package/roles/gnc-engineer/templates/gnc-report-template.md +105 -0
  108. package/roles/gnc-engineer/tests/test_gnc_engineer_core.py +238 -0
  109. package/roles/gnc-engineer/tests/test_role_gnc_engineer.py +142 -0
  110. package/roles/guidance-engineer/ROLE.md +156 -0
  111. package/roles/guidance-engineer/SOURCES.md +43 -0
  112. package/roles/guidance-engineer/cli.py +280 -0
  113. package/roles/guidance-engineer/core/guidance_core.py +820 -0
  114. package/roles/guidance-engineer/templates/guidance-report-template.md +70 -0
  115. package/roles/guidance-engineer/tests/test_guidance_engineer_core.py +269 -0
  116. package/roles/guidance-engineer/tests/test_role_guidance_engineer.py +146 -0
  117. package/roles/high-speed-aerodynamics-engineer/ROLE.md +161 -0
  118. package/roles/high-speed-aerodynamics-engineer/SOURCES.md +18 -0
  119. package/roles/high-speed-aerodynamics-engineer/cli.py +282 -0
  120. package/roles/high-speed-aerodynamics-engineer/core/high_speed_aero_core.py +1566 -0
  121. package/roles/high-speed-aerodynamics-engineer/templates/high-speed-memo-template.md +75 -0
  122. package/roles/high-speed-aerodynamics-engineer/tests/test_bundle_protocol.py +103 -0
  123. package/roles/high-speed-aerodynamics-engineer/tests/test_high_speed_aero_core.py +280 -0
  124. package/roles/high-speed-aerodynamics-engineer/tests/test_role_high_speed_aerodynamics.py +125 -0
  125. package/roles/ndt-engineer/ROLE.md +157 -0
  126. package/roles/ndt-engineer/SOURCES.md +18 -0
  127. package/roles/ndt-engineer/cli.py +316 -0
  128. package/roles/ndt-engineer/core/ndt_core.py +1590 -0
  129. package/roles/ndt-engineer/templates/ndt-plan-template.md +109 -0
  130. package/roles/ndt-engineer/tests/test_ndt_cli_bundle.py +183 -0
  131. package/roles/ndt-engineer/tests/test_ndt_core.py +437 -0
  132. package/roles/ndt-engineer/tests/test_role_ndt_engineer.py +148 -0
  133. package/roles/numerical-analysis-engineer/ROLE.md +158 -0
  134. package/roles/numerical-analysis-engineer/SOURCES.md +19 -0
  135. package/roles/numerical-analysis-engineer/cli.py +414 -0
  136. package/roles/numerical-analysis-engineer/core/numerical_analysis_core.py +1220 -0
  137. package/roles/numerical-analysis-engineer/templates/verification-memo-template.md +120 -0
  138. package/roles/numerical-analysis-engineer/tests/test_bundle_protocol.py +157 -0
  139. package/roles/numerical-analysis-engineer/tests/test_numerical_analysis_core.py +337 -0
  140. package/roles/numerical-analysis-engineer/tests/test_role_numerical_analysis_engineer.py +117 -0
  141. package/roles/propulsion-engineer/ROLE.md +163 -0
  142. package/roles/propulsion-engineer/SOURCES.md +15 -0
  143. package/roles/propulsion-engineer/cli.py +168 -0
  144. package/roles/propulsion-engineer/core/propulsion_core.py +1291 -0
  145. package/roles/propulsion-engineer/templates/propulsion-report-template.md +92 -0
  146. package/roles/propulsion-engineer/tests/test_propulsion_engineer_core.py +294 -0
  147. package/roles/propulsion-engineer/tests/test_role_propulsion_engineer.py +106 -0
  148. package/roles/space-systems-engineer/ROLE.md +161 -0
  149. package/roles/space-systems-engineer/SOURCES.md +49 -0
  150. package/roles/space-systems-engineer/cli.py +173 -0
  151. package/roles/space-systems-engineer/core/space_systems_core.py +980 -0
  152. package/roles/space-systems-engineer/templates/space-systems-template.md +88 -0
  153. package/roles/space-systems-engineer/tests/test_role_space_systems_engineer.py +117 -0
  154. package/roles/space-systems-engineer/tests/test_space_systems_engineer_core.py +247 -0
  155. package/roles/state-estimation-engineer/ROLE.md +154 -0
  156. package/roles/state-estimation-engineer/SOURCES.md +27 -0
  157. package/roles/state-estimation-engineer/cli.py +326 -0
  158. package/roles/state-estimation-engineer/core/state_estimation_core.py +958 -0
  159. package/roles/state-estimation-engineer/templates/nav-state-estimator-design-template.md +76 -0
  160. package/roles/state-estimation-engineer/tests/test_bundle_protocol.py +110 -0
  161. package/roles/state-estimation-engineer/tests/test_role_state_estimation_engineer.py +127 -0
  162. package/roles/state-estimation-engineer/tests/test_state_estimation_engineer_core.py +231 -0
  163. package/roles/structures-loads-engineer/ROLE.md +171 -0
  164. package/roles/structures-loads-engineer/SOURCES.md +44 -0
  165. package/roles/structures-loads-engineer/cli.py +219 -0
  166. package/roles/structures-loads-engineer/core/structures_loads_core.py +1112 -0
  167. package/roles/structures-loads-engineer/templates/loads-strength-report-template.md +96 -0
  168. package/roles/structures-loads-engineer/tests/test_bundle_protocol.py +101 -0
  169. package/roles/structures-loads-engineer/tests/test_role_structures_loads_engineer.py +147 -0
  170. package/roles/structures-loads-engineer/tests/test_structures_loads_core.py +261 -0
@@ -0,0 +1,43 @@
1
+ # SOURCES.md - Guidance Engineer
2
+
3
+ | Source | Role use | Gated |
4
+ |---|---|---|
5
+ | ARP4754A | development-assurance context framing the guidance cluster leaves (reference only) | true |
6
+ | FAR-25 / CS-25 | airworthiness context where guided-vehicle development touches civil certification (reference only) | false |
7
+ | Zarchan, Tactical and Strategic Missile Guidance (AIAA) | classical PN/APN/ZEM guidance-law context (book, summarized via leaves) | true |
8
+ | Siouris, Missile Guidance and Control Systems | pursuit/CLOS/guidance-loop context (book, summarized via leaves) | true |
9
+
10
+ Summary-not-copy per STANDARDS.md. The guidance laws themselves are
11
+ common knowledge encoded by the bound Aero Agent Skills leaves; no
12
+ standard or book text is reproduced.
13
+
14
+ ## Encoded domain rules (computed by core/guidance_core.py)
15
+
16
+ The engine's numbers come from the laws carried by the bound
17
+ gnc-autonomy/guidance leaves (each leaf is a paraphrase of common
18
+ guidance knowledge); anchors are asserted in the leaf gate-3 contract
19
+ tests and reproduced here:
20
+
21
+ | Rule | Formula | Bound leaf | Anchor |
22
+ |---|---|---|---|
23
+ | Closing velocity | vc = -(rx*vx + ry*vy) / r | guidance/proportional-navigation | 199.007 m/s |
24
+ | LOS rate | lam_dot = (rx*vy - ry*vx) / r^2 | guidance/proportional-navigation | 0.019802 rad/s |
25
+ | PN command | a_c = N * vc * lam_dot | guidance/proportional-navigation | 15.762965 m/s^2 (N=4) |
26
+ | APN command | a_apn = N' * (Vc*lam_dot + a_T/2), N' = 4 default | guidance/augmented-proportional-navigation | 55.763 m/s^2 |
27
+ | Acceleration in g | a / g0, g0 = 9.80665 | guidance/augmented-proportional-navigation | 1.607 g |
28
+ | Pursuit heading error | eta = wrap(lam - psi) | guidance/pursuit-guidance | 5.711 deg |
29
+ | Pursuit lead angle | asin((Vt/Vi) sin(beta)) | guidance/pursuit-guidance | -1.901 deg |
30
+ | Capture condition | Vi > Vt | guidance/pursuit-guidance | satisfied |
31
+ | Tail-chase intercept | t_i = r / (Vi - Vt) | guidance/pursuit-guidance | 5.025 s |
32
+ | Desired course (waypoint) | psi_d = atan2(wy - py, wx - px) | guidance/midcourse-guidance | 26.565 deg |
33
+ | Turn-rate-limited steering | psi_c = psi + clamp(e, +/-omega*dt) | guidance/midcourse-guidance | 5 deg per step |
34
+ | Velocity to be gained | vgo = max(0, V_target - V cos(e)) | guidance/midcourse-guidance | 65.08 m/s |
35
+ | Zero-effort miss | ZEM = \|rho + v_rel t_go\|, t_go = max(0, -(rho.v_rel)/\|v_rel\|^2) | guidance/midcourse-guidance | 150 m at t_go 20 s |
36
+ | Miss requirement check | ZEM <= required miss distance | guidance/midcourse-guidance | 10 m req (example) |
37
+ | Acceleration limit | utilization = \|a_c\| / (limit_g * g0) | guidance/augmented-proportional-navigation | 5.4% PN / 19.0% APN |
38
+
39
+ All example engagement numbers (speeds, ranges, limits, requirements)
40
+ are stated project facts for the reference item — verify against the
41
+ vehicle and target data before use. Navigation constant band 3-5 with
42
+ 4 as the common baseline is the leaf-encoded practice note; the
43
+ selected N remains a project decision recorded in the report.
@@ -0,0 +1,280 @@
1
+ #!/usr/bin/env python3
2
+ """cli.py - run the Guidance Engineer role.
3
+
4
+ Usage:
5
+ python3 cli.py build [--out <file.md>] [--n-nav <N>] \\
6
+ [--miss-requirement <m>] [--accel-limit <g>] [--bundle]
7
+ # build the Guidance Law Design and Assessment Report for the
8
+ # example item (interceptor terminal homing loop)
9
+ # --bundle also emit evidence/{model,gates,provenance}.json (PROTOCOL.md)
10
+ python3 cli.py check --file <file.md> # gate-check a report
11
+
12
+ The role ENGINE (core/guidance_core.py) does the work standalone; bound
13
+ Aero Agent Skills leaves in gnc-autonomy/guidance carry the same laws as
14
+ *_logic.py files, and when the library is present their implementations
15
+ are dispatched on the same inputs and recorded in provenance.json
16
+ (delta ~ 0 = two independent implementations agree).
17
+ """
18
+ import argparse
19
+ import importlib.util
20
+ import json
21
+ import os
22
+ import sys
23
+
24
+ sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)),
25
+ "core"))
26
+ sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)),
27
+ "..", "..", "scripts"))
28
+ import guidance_core as core # noqa: E402
29
+ import evidence # noqa: E402
30
+
31
+ ROLE_SLUG = "guidance-engineer"
32
+
33
+ # Functions the core exports with leaf-identical signatures, dispatched
34
+ # against the bound leaf *_logic.py when the skills tree is present.
35
+ DISPATCH_ROWS = [
36
+ {
37
+ "leaf": "gnc-autonomy/guidance/proportional-navigation",
38
+ "skill_fn": "commanded_acceleration",
39
+ "core_fn": "commanded_acceleration",
40
+ "kwargs": {"rx": 1000.0, "ry": 100.0, "vx": -200.0, "vy": 0.0,
41
+ "n_nav": 4.0},
42
+ "tolerance": 1e-6,
43
+ },
44
+ {
45
+ "leaf": "gnc-autonomy/guidance/augmented-proportional-navigation",
46
+ "skill_fn": "apn_command",
47
+ "core_fn": "apn_command",
48
+ "kwargs": {"navigation_ratio": 4.0,
49
+ "closing_velocity": 199.007438041998,
50
+ "los_rate": 0.019801980198,
51
+ "target_lateral_accel": 20.0},
52
+ "tolerance": 1e-6,
53
+ },
54
+ {
55
+ "leaf": "gnc-autonomy/guidance/pursuit-guidance",
56
+ "skill_fn": "heading_error",
57
+ "core_fn": "heading_error",
58
+ "kwargs": {"psi": 0.0, "rx": 1000.0, "ry": 100.0},
59
+ "tolerance": 1e-6,
60
+ },
61
+ {
62
+ "leaf": "gnc-autonomy/guidance/midcourse-guidance",
63
+ "skill_fn": "desired_heading",
64
+ "core_fn": "desired_heading",
65
+ "kwargs": {"position": (0.0, 0.0), "waypoint": (1000.0, 500.0)},
66
+ "tolerance": 1e-6,
67
+ },
68
+ {
69
+ "leaf": "gnc-autonomy/guidance/midcourse-guidance",
70
+ "skill_fn": "zero_effort_miss",
71
+ "core_fn": "zero_effort_miss",
72
+ "kwargs": {"interceptor_pos": (0.0, 0.0),
73
+ "interceptor_vel": (300.0, 0.0),
74
+ "target_pos": (6000.0, 150.0),
75
+ "target_vel": (0.0, 0.0)},
76
+ "value_key": "zem",
77
+ "tolerance": 1e-6,
78
+ },
79
+ ]
80
+
81
+
82
+ def _load_skill_logic(leaf, skills_root):
83
+ """Import the leaf's *_logic.py module (stdlib) or return None.
84
+
85
+ Returns (module, logic_file_basename) or (None, None)."""
86
+ logic_dir = os.path.join(skills_root, "skills", leaf, "scripts")
87
+ if not os.path.isdir(logic_dir):
88
+ return None, None
89
+ logic_files = sorted(f for f in os.listdir(logic_dir)
90
+ if f.endswith("_logic.py"))
91
+ for lf in logic_files:
92
+ spec = importlib.util.spec_from_file_location(
93
+ "role_dispatch_" + leaf.replace("/", "_"),
94
+ os.path.join(logic_dir, lf))
95
+ if spec is None or spec.loader is None:
96
+ continue
97
+ mod = importlib.util.module_from_spec(spec)
98
+ try:
99
+ spec.loader.exec_module(mod)
100
+ except Exception:
101
+ continue
102
+ return mod, lf
103
+ return None, None
104
+
105
+
106
+ def _dispatch_rows(skills_root=""):
107
+ """Dispatch each row against the bound leaf and the role core.
108
+
109
+ Returns provenance rows: dispatched True with core/skill value,
110
+ delta and agrees when the leaf logic is reachable, else a
111
+ dispatched False row (role core ran standalone - still valid).
112
+ """
113
+ root = skills_root or os.environ.get(
114
+ "AEROSKILLS_DEV", os.path.expanduser("~/AeroSkills"))
115
+ rows = []
116
+ for row in DISPATCH_ROWS:
117
+ mod, logic_file = _load_skill_logic(row["leaf"], root)
118
+ if mod is None or not hasattr(mod, row["skill_fn"]):
119
+ rows.append({
120
+ "leaf": row["leaf"],
121
+ "logic_file": "n/a (standalone)",
122
+ "function": row["skill_fn"],
123
+ "dispatched": False,
124
+ "reason": "skill logic unavailable (standalone build)",
125
+ })
126
+ continue
127
+ try:
128
+ skill_val = getattr(mod, row["skill_fn"])(**row["kwargs"])
129
+ core_val = getattr(core, row["core_fn"])(**row["kwargs"])
130
+ if row.get("value_key"):
131
+ skill_val = skill_val[row["value_key"]]
132
+ core_val = core_val[row["value_key"]]
133
+ if not isinstance(skill_val, (int, float)) or \
134
+ not isinstance(core_val, (int, float)):
135
+ raise TypeError("non-numeric cross-check value")
136
+ delta = abs(float(core_val) - float(skill_val))
137
+ rows.append({
138
+ "leaf": row["leaf"],
139
+ "logic_file": "scripts/" + (logic_file or "unknown.py"),
140
+ "function": row["skill_fn"],
141
+ "dispatched": True,
142
+ "core_value": round(float(core_val), 6),
143
+ "skill_value": round(float(skill_val), 6),
144
+ "delta": round(delta, 9),
145
+ "agrees": delta <= row["tolerance"],
146
+ "tolerance": row["tolerance"],
147
+ })
148
+ except Exception as exc: # noqa: BLE001 - provenance must not crash
149
+ rows.append({
150
+ "leaf": row["leaf"],
151
+ "function": row["skill_fn"],
152
+ "dispatched": False,
153
+ "reason": "dispatch error: %s" % exc,
154
+ })
155
+ return rows
156
+
157
+
158
+ def _provenance():
159
+ """Provenance for the bundle: role core + leaf dispatch rows."""
160
+ rows = _dispatch_rows()
161
+ return {
162
+ "role": ROLE_SLUG,
163
+ "core": {
164
+ "file": "core/guidance_core.py",
165
+ "functions": ["commanded_acceleration", "apn_command",
166
+ "heading_error", "lead_angle", "desired_heading",
167
+ "commanded_heading", "zero_effort_miss",
168
+ "accel_utilization", "miss_within_requirement",
169
+ "build_report", "check_report",
170
+ "render_report_markdown"],
171
+ "version": "0.1.0",
172
+ },
173
+ "skills": rows,
174
+ "cross_checked": any(r.get("dispatched") for r in rows),
175
+ "disclaimer": "DRAFT for human review. Not an approval document.",
176
+ }
177
+
178
+
179
+ def _model_jsonable(model):
180
+ """Deep-copy the model into plain JSON types (tuples -> lists)."""
181
+ return json.loads(json.dumps(model, default=list))
182
+
183
+
184
+ def cmd_build(args):
185
+ proj = core.example_project()
186
+ if args.n_nav:
187
+ proj.n_nav = float(args.n_nav)
188
+ if args.miss_requirement:
189
+ proj.miss_requirement_m = float(args.miss_requirement)
190
+ if args.accel_limit:
191
+ proj.accel_limit_g = float(args.accel_limit)
192
+ model = core.build_report(proj)
193
+ md = core.render_report_markdown(model)
194
+
195
+ # program profile (customer tailoring): load + apply context header
196
+ profile = None
197
+ try:
198
+ profile = evidence.load_profile(args.profile)
199
+ except ValueError as exc:
200
+ print("error: bad profile: %s" % exc)
201
+ return 1
202
+ if profile:
203
+ model["profile"] = {
204
+ "customer": profile.get("customer"),
205
+ "program": profile.get("program"),
206
+ "basis": profile.get("basis"),
207
+ "authority": profile.get("authority"),
208
+ "der": profile.get("der"),
209
+ "document_prefix": profile.get("document_prefix"),
210
+ "revision": profile.get("revision"),
211
+ }
212
+ md = evidence.profile_header_block(profile) + md
213
+
214
+ gates = core.check_report(model)
215
+ if args.out:
216
+ with open(args.out, "w") as f:
217
+ f.write(md)
218
+ print("built Guidance Law Design and Assessment Report (%s) -> %s"
219
+ % (model["item"], args.out))
220
+ if profile:
221
+ print("profile: %s / %s" % (profile.get("customer"),
222
+ profile.get("program")))
223
+ print("gates: all_pass=%s %s" % (gates["all_pass"], gates))
224
+ if args.bundle:
225
+ paths = evidence.write_bundle(
226
+ args.out, ROLE_SLUG,
227
+ "Guidance Law Design and Assessment Report",
228
+ _model_jsonable(model), gates, _provenance())
229
+ print("bundle: model=%s" % paths["model"])
230
+ print(" gates=%s" % paths["gates"])
231
+ print(" provenance=%s" % paths["provenance"])
232
+ else:
233
+ print(md)
234
+ return 0 if gates["all_pass"] else 1
235
+
236
+
237
+ def cmd_check(args):
238
+ if not os.path.exists(args.file):
239
+ print("file not found: %s" % args.file)
240
+ return 1
241
+ md = open(args.file).read()
242
+ gates = core.check_report_markdown(md)
243
+ for name, ok in gates.items():
244
+ if name != "all_pass":
245
+ print("%s %s" % ("PASS" if ok else "FAIL", name))
246
+ print("RESULT: %s" % ("PASS" if gates["all_pass"] else "FAIL"))
247
+ return 0 if gates["all_pass"] else 1
248
+
249
+
250
+ def main():
251
+ p = argparse.ArgumentParser(description="Guidance Engineer role")
252
+ sub = p.add_subparsers(dest="cmd", required=True)
253
+
254
+ b = sub.add_parser("build")
255
+ b.add_argument("--out", default="", help="output file (default: stdout)")
256
+ b.add_argument("--n-nav", default="",
257
+ help="navigation constant N (default 4)")
258
+ b.add_argument("--miss-requirement", default="",
259
+ help="miss distance requirement in m (default 10)")
260
+ b.add_argument("--accel-limit", default="",
261
+ help="vehicle lateral acceleration limit in g (default 30)")
262
+ b.add_argument("--bundle", action="store_true",
263
+ help="emit evidence/{model,gates,provenance}.json")
264
+ b.add_argument("--profile", default="",
265
+ help="program profile JSON (customer tailoring, "
266
+ "docs/PROFILE-SCHEMA.md)")
267
+ b.set_defaults(fn=cmd_build)
268
+
269
+ c = sub.add_parser("check")
270
+ c.add_argument("--file", required=True,
271
+ help="Guidance Law Design and Assessment Report markdown "
272
+ "to gate-check")
273
+ c.set_defaults(fn=cmd_check)
274
+
275
+ args = p.parse_args()
276
+ sys.exit(args.fn(args))
277
+
278
+
279
+ if __name__ == "__main__":
280
+ main()