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,158 @@
1
+ ---
2
+ type: role
3
+ name: numerical-analysis-engineer
4
+ title: "Numerical Analysis Engineer"
5
+ status: draft
6
+ domain: cross-cutting
7
+ deliverable_type: "numerical methods verification memo"
8
+ standards_bound:
9
+ - id: asme-vv-20
10
+ tier: TIER-2
11
+ reference-only: true
12
+ skills_bound:
13
+ - cross-cutting/numerics/numerical-integration
14
+ - cross-cutting/numerics/root-finding
15
+ - cross-cutting/numerics/convergence-verification
16
+ - cross-cutting/numerics/finite-difference-derivatives
17
+ - cross-cutting/numerics/ode-solvers
18
+ - cross-cutting/numerics/interpolation
19
+ - cross-cutting/numerics/least-squares-regression
20
+ - cross-cutting/numerics/matrix-operations
21
+ - cross-cutting/numerics/singular-value-decomposition
22
+ - cross-cutting/numerics/uncertainty-propagation
23
+ tools_allowed: [stdlib, offline-file-processing]
24
+ forbidden:
25
+ - "issue engineering approval"
26
+ - "declare compliance"
27
+ - "sign off numerical results as released"
28
+ - "reproduce proprietary standard text"
29
+ sign_off_required: true
30
+ license: Apache-2.0
31
+ compatibility: "agentskills.io ROLE.md; any SKILL.md host"
32
+ metadata:
33
+ version: 0.1.0
34
+ author: ashfordeOU
35
+ skills_release: "v1.3.0+"
36
+ ---
37
+
38
+ # Numerical Analysis Engineer
39
+
40
+ ## Role identity
41
+
42
+ This role independently VERIFIES the numerical methods inside an
43
+ engineering code: the integrals, roots, solves, derivatives and
44
+ convergence behavior that the code's results quietly depend on. It
45
+ selects reference methods with real rationale, recomputes every
46
+ reported number with an independent implementation, quantifies the
47
+ discrepancy against the code's own claimed tolerances, and runs a
48
+ three-mesh convergence study plus a conditioning report to bound the
49
+ discretization and rounding error. Use when an engineering deliverable
50
+ rests on a numerical result that must be checked against reference
51
+ methods. Do NOT use for design decisions themselves (use the domain
52
+ roles), for statistical data analysis (see the analysis roles), or to
53
+ approve/release code.
54
+
55
+ ## Deliverable contract
56
+
57
+ 1. **Numerical Methods Verification Memo** — per numerical operation in
58
+ scope: the reference method selected and why, the independent
59
+ reference value, the code-reported value, the quantified discrepancy
60
+ vs the code's claimed tolerance, a PASS/FAIL verdict, and (for any
61
+ FAIL) an evidence-backed finding with a recommendation. Always
62
+ includes the convergence study (observed order, Richardson
63
+ extrapolation, GCI) and the conditioning assessment of any linear
64
+ solve.
65
+ 2. **Evidence bundle** — `evidence/{model,gates,provenance}.json`
66
+ emitted with `--bundle` per PROTOCOL.md v1, carrying every number in
67
+ the memo plus the skill-dispatch cross-check rows.
68
+
69
+ ## Workflow (stages -> bound skills)
70
+
71
+ | Stage | Loads skill | Produces |
72
+ |---|---|---|
73
+ | 1. Integration | numerics/numerical-integration | rule selection + reference integral + Richardson error estimate |
74
+ | 2. Root finding | numerics/root-finding | reference root (Newton + bisection bracket cross-check), simple-root check |
75
+ | 3. Derivatives | numerics/finite-difference-derivatives | stencil-selection input when code uses numerical differentiation |
76
+ | 4. ODE paths | numerics/ode-solvers | reference time-marching when the code integrates state equations |
77
+ | 5. Interpolation paths | numerics/interpolation | reference table interpolation checks |
78
+ | 6. Regression paths | numerics/least-squares-regression | reference fit checks |
79
+ | 7. Linear solve | numerics/matrix-operations + numerics/singular-value-decomposition | reference solve, residual, condition number s_max/s_min |
80
+ | 8. Convergence | numerics/convergence-verification | observed order, Richardson extrapolation, GCI (Fs=1.25), verdict |
81
+ | 9. Error budget | numerics/uncertainty-propagation | uncertainty framing for inputs when claimed |
82
+ | 10. Memo build | (report conventions) | the Numerical Methods Verification Memo + evidence bundle |
83
+
84
+ ## Evidence gates
85
+
86
+ - Stage 1 done = every in-scope integral has a reference rule with
87
+ rationale and an error estimate; the reference value is a real number.
88
+ - Stage 2 done = every in-scope root has a reference root, a bracket or
89
+ guess statement, and a residual.
90
+ - Stage 7 done = the solve is checked against a pivoted reference solve
91
+ and the conditioning report is stated.
92
+ - Stage 8 done = a three-mesh study reports observed order and GCI (or
93
+ an explicit oscillatory/diverging verdict).
94
+ - FINAL = every check row quantifies discrepancy vs the code's claimed
95
+ tolerance and carries a PASS/FAIL verdict; findings cite their
96
+ evidence. Nothing is asserted without a checkable basis.
97
+
98
+ ## Boundary / forbidden
99
+
100
+ - NEVER issue engineering approval, declare compliance, or sign off
101
+ numerical results as released — a human numerical-analysis reviewer
102
+ owns the disposition of findings.
103
+ - NEVER reproduce proprietary standard text (ASME V&V 20-2009 is
104
+ reference-only); the memo's method descriptions are original
105
+ paraphrases of classical numerical analysis.
106
+ - The code's claimed tolerances are inputs to the memo, not
107
+ endorsements.
108
+ - Output is a DRAFT for human review — mark it draft.
109
+
110
+ ## Verification
111
+
112
+ The role runs STANDALONE: `core/numerical_analysis_core.py` is an
113
+ executable engine that reproduces the bound leaf methods (composite
114
+ trapezoid/Simpson, Richardson error estimate error(2n)=|I_2n-I_n|/3,
115
+ Newton-Raphson and bisection, Gaussian elimination with partial
116
+ pivoting, observed order/GCI with Fs=1.25, condition number s_max/s_min)
117
+ to compute reference values, quantify discrepancies, run the
118
+ convergence study, and gate-check deliverables. No AeroSkills checkout
119
+ required (`AEROSKILLS_DEV=/nonexistent` still builds correctly).
120
+
121
+ Run the role:
122
+ ```bash
123
+ python3 cli.py build --out memo.md # example item
124
+ python3 cli.py build --out memo.md --bundle # + evidence bundle
125
+ python3 cli.py build --code-integral 50400.0 --code-integral-tol 1.0 \
126
+ --out memo.md # re-verify a code report
127
+ python3 cli.py build --profile ../../profiles/example-airframer.json \
128
+ --out memo.md # program tailoring
129
+ python3 cli.py check --file memo.md # gate-check a memo
130
+ ```
131
+
132
+ Tests:
133
+ - tests/test_numerical_analysis_core.py: quadrature rules + Richardson
134
+ error estimate, Newton/bisection root anchors (A/A* = 1.2 subsonic
135
+ M = 0.59024876099), reference solve, convergence study, conditioning,
136
+ memo builder, gates, standalone (no skills repo), and the filled
137
+ template byte-matches the core example.
138
+ - tests/test_role_numerical_analysis_engineer.py: bound-skill
139
+ resolution (skips if the skills repo is absent) + workflow +
140
+ template present + boundaries.
141
+ - tests/test_bundle_protocol.py: `build --bundle` emits the three
142
+ evidence files with all gates PASS; the ten dispatch rows agree with
143
+ the bound numerics leaves (delta <= 1e-6) when AeroSkills is present;
144
+ standalone runs honest (`cross_checked: false`); `--profile` and
145
+ `check` behave per protocol.
146
+
147
+ Bound skills in Aero Agent Skills deepen individual stages when the
148
+ library is present (integration, root-finding, convergence, matrix and
149
+ SVD leaves); the core engine does not depend on them.
150
+
151
+ ## Compliance
152
+
153
+ - asme-vv-20 TIER-2 reference-only (discretization-error vocabulary).
154
+ - Classical numerical-analysis methodology (Richardson 1910/1927,
155
+ Euler-Maclaurin error analysis, Newton/Simpson/Gauss quadrature) is
156
+ public domain and paraphrased, never copied from any commercial
157
+ text.
158
+ - SOURCES.md records standards referenced.
@@ -0,0 +1,19 @@
1
+ # SOURCES.md - Numerical Analysis Engineer
2
+
3
+ Standards and documents this role references. Summary-not-copy: see
4
+ [STANDARDS.md](../../STANDARDS.md) for the full rule and purchase links.
5
+ The numerical methods themselves (Newton, Simpson/Gauss quadrature,
6
+ Richardson extrapolation, Gaussian elimination, condition numbers) are
7
+ classical analysis and public-domain methodology; the bound AeroSkills
8
+ leaves paraphrase them and anchor their tables.
9
+
10
+ | Source | Role use | Gated |
11
+ |---|---|---|
12
+ | ASME V&V 20-2009, Standard for Verification and Validation in CFD and Heat Transfer | discretization-error vocabulary (observed order, Richardson extrapolation, grid convergence index, safety factor) used in the memo's convergence section | true |
13
+ | Classical numerical analysis (Newton 1669/1711; Simpson 1743; Gauss 1814; Richardson 1910/1927; Euler-Maclaurin error analysis) | reference methods: quadrature rules, root iterations, error estimates - public domain, paraphrased | false |
14
+ | NACA Report 824 (public-domain anchor of the cross-cutting/numerics leaves) | compressible-flow relation context for the area-Mach inversion anchor used by the root-finding leaf | false |
15
+
16
+ The verification-memo template in `templates/` is an original
17
+ structure produced by the role's own core engine; it contains no
18
+ reproduced text from any standard. ASME V&V 20-2009 is referenced for
19
+ its methodology vocabulary only (summary-not-copy, TIER-2).
@@ -0,0 +1,414 @@
1
+ #!/usr/bin/env python3
2
+ """cli.py - run the Numerical Analysis Engineer role.
3
+
4
+ Usage:
5
+ python3 cli.py build [--out <file.md>] [--bundle] [--profile <file.json>]
6
+ [--no-dispatch]
7
+ [--code-integral <N>] [--code-mach <M>]
8
+ [--code-solve <c0,c1>]
9
+ # build the Numerical Methods Verification Memo for the example item
10
+ # (Loads Post-Processor v2.3.1) and gate-check it
11
+ # --bundle also emit evidence/{model,gates,provenance}.json (PROTOCOL.md)
12
+ # --profile program profile JSON for customer tailoring
13
+ # (docs/PROFILE-SCHEMA.md) -> prepended context header
14
+ # --no-dispatch skip dispatching bound AeroSkills numerics logic
15
+ # the --code-* flags re-verify a different code report of the same
16
+ # operations (integration value, Mach root, linear-solve result)
17
+ python3 cli.py check --file <file.md> # gate-check an existing memo
18
+
19
+ The role ENGINE (core/numerical_analysis_core.py) does the work standalone:
20
+ reference quadrature, Newton/bisection root finding, dense solve,
21
+ convergence study (observed order, Richardson, GCI), conditioning, and
22
+ the evidence gates. When AeroSkills is present (AEROSKILLS_DEV or
23
+ ~/AeroSkills), the CLI dispatches the bound cross-cutting/numerics leaf
24
+ logic modules and cross-checks their numbers against the core's - two
25
+ independent implementations agreeing is recorded in provenance.json.
26
+ """
27
+ import argparse
28
+ import importlib.util
29
+ import os
30
+ import sys
31
+
32
+ sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)),
33
+ "core"))
34
+ sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)),
35
+ "..", "..", "scripts"))
36
+ import numerical_analysis_core as core # noqa: E402
37
+ import evidence # noqa: E402
38
+
39
+ AEROSKILLS = os.environ.get("AEROSKILLS_DEV", os.path.expanduser("~/AeroSkills"))
40
+ NUMERICS_ROOT = os.path.join(AEROSKILLS, "skills", "cross-cutting", "numerics")
41
+ ROLE_SLUG = "numerical-analysis-engineer"
42
+ SKILLS_RELEASE = "v1.3.0+"
43
+
44
+ # Bound leaves and their dispatch points (leaf module names as on disk).
45
+ BOUND_LEAVES = [
46
+ ("numerical-integration", "numerical_integration_logic.py"),
47
+ ("root-finding", "root_finding_logic.py"),
48
+ ("convergence-verification", "convergence_verification_logic.py"),
49
+ ("singular-value-decomposition", "singular_value_decomposition_logic.py"),
50
+ ("matrix-operations", "matrix_operations_logic.py"),
51
+ ]
52
+
53
+
54
+ def _load_leaf(leaf, logic_file):
55
+ """Import a bound leaf's *_logic.py module, or None when unavailable."""
56
+ path = os.path.join(NUMERICS_ROOT, leaf, "scripts", logic_file)
57
+ if not os.path.exists(path):
58
+ return None
59
+ spec = importlib.util.spec_from_file_location(
60
+ "numerics_leaf_" + leaf.replace("-", "_"), path)
61
+ mod = importlib.util.module_from_spec(spec)
62
+ try:
63
+ spec.loader.exec_module(mod)
64
+ return mod
65
+ except Exception:
66
+ return None
67
+
68
+
69
+ def _row(leaf, logic_file, fn, core_val, skill_val, point, tol=1e-6):
70
+ delta = abs(float(core_val) - float(skill_val))
71
+ return {
72
+ "leaf": "cross-cutting/numerics/" + leaf,
73
+ "skill_md": "SKILL.md",
74
+ "logic_file": "scripts/" + logic_file,
75
+ "function": fn,
76
+ "dispatched": True,
77
+ "cross_check_point": point,
78
+ "core_value": round(float(core_val), 9),
79
+ "skill_value": round(float(skill_val), 9),
80
+ "delta": round(delta, 12),
81
+ "agrees": delta <= tol,
82
+ "tolerance": tol,
83
+ "skills_release": SKILLS_RELEASE,
84
+ }
85
+
86
+
87
+ def _row_err(leaf, logic_file, fn, exc):
88
+ return {"leaf": "cross-cutting/numerics/" + leaf,
89
+ "logic_file": "scripts/" + logic_file,
90
+ "function": fn, "dispatched": True,
91
+ "error": str(exc), "agrees": False}
92
+
93
+
94
+ def _run_crosschecks(item, model):
95
+ """Cross-check core numbers against the five bound numerics leaves.
96
+
97
+ Each row compares the same quantity computed two independent ways:
98
+ the role core implementation and the AeroSkills leaf logic. Rows
99
+ are empty when the skills library is absent.
100
+ """
101
+ rows = []
102
+ a, b = float(item.interval[0]), float(item.interval[1])
103
+ f = lambda y: core.lift_intensity(y, item.integrand_w0,
104
+ item.integrand_span)
105
+ ar, gam = item.area_ratio, item.gamma
106
+ resid = lambda M: core.area_mach_residual(M, ar, gam)
107
+ deriv = lambda M: core.area_mach_derivative(M, gam)
108
+
109
+ # 1. numerical-integration: trapezoid at the code's panel count
110
+ mod = _load_leaf("numerical-integration",
111
+ "numerical_integration_logic.py")
112
+ if mod is not None and hasattr(mod, "trapezoid"):
113
+ try:
114
+ core_val = core.trapezoid(f, a, b, item.code_integral_n)
115
+ skill_val = mod.trapezoid(f, a, b, item.code_integral_n)
116
+ rows.append(_row("numerical-integration",
117
+ "numerical_integration_logic.py", "trapezoid",
118
+ core_val, skill_val,
119
+ "code-panel trapezoid of the spanwise load"))
120
+ except Exception as e: # pragma: no cover
121
+ rows.append(_row_err("numerical-integration",
122
+ "numerical_integration_logic.py",
123
+ "trapezoid", e))
124
+
125
+ # 2. numerical-integration: reference Simpson value
126
+ if mod is not None and hasattr(mod, "simpson"):
127
+ try:
128
+ n_ref = max(2 * item.code_integral_n, 64)
129
+ core_val = core.simpson(f, a, b, n_ref)
130
+ skill_val = mod.simpson(f, a, b, n_ref)
131
+ rows.append(_row("numerical-integration",
132
+ "numerical_integration_logic.py", "simpson",
133
+ core_val, skill_val,
134
+ "reference Simpson integral of the spanwise "
135
+ "load"))
136
+ except Exception as e: # pragma: no cover
137
+ rows.append(_row_err("numerical-integration",
138
+ "numerical_integration_logic.py",
139
+ "simpson", e))
140
+
141
+ # 3. numerical-integration: Richardson error estimate error(2n)
142
+ if mod is not None and hasattr(mod, "error_estimate_trapezoid"):
143
+ try:
144
+ core_val = core.richardson_error_trapezoid(
145
+ f, a, b, item.code_integral_n)
146
+ skill_val = mod.error_estimate_trapezoid(
147
+ f, a, b, item.code_integral_n)
148
+ rows.append(_row("numerical-integration",
149
+ "numerical_integration_logic.py",
150
+ "error_estimate_trapezoid", core_val, skill_val,
151
+ "Richardson error estimate of the trapezoid "
152
+ "result"))
153
+ except Exception as e: # pragma: no cover
154
+ rows.append(_row_err("numerical-integration",
155
+ "numerical_integration_logic.py",
156
+ "error_estimate_trapezoid", e))
157
+
158
+ # 4. root-finding: Newton-Raphson subsonic area-Mach root
159
+ mod = _load_leaf("root-finding", "root_finding_logic.py")
160
+ if mod is not None and hasattr(mod, "newton_raphson"):
161
+ try:
162
+ core_val = core.newton_raphson(resid, deriv, item.newton_guess)
163
+ skill_val = mod.newton_raphson(resid, deriv, item.newton_guess)
164
+ rows.append(_row("root-finding", "root_finding_logic.py",
165
+ "newton_raphson", core_val, skill_val,
166
+ "subsonic root of A/A* = %g, gamma = %g"
167
+ % (ar, gam)))
168
+ except Exception as e: # pragma: no cover
169
+ rows.append(_row_err("root-finding", "root_finding_logic.py",
170
+ "newton_raphson", e))
171
+
172
+ # 5. root-finding: bisection bracket cross-check
173
+ if mod is not None and hasattr(mod, "bisection"):
174
+ try:
175
+ core_val = core.bisection(resid, item.bracket[0], item.bracket[1])
176
+ skill_val = mod.bisection(resid, item.bracket[0], item.bracket[1])
177
+ rows.append(_row("root-finding", "root_finding_logic.py",
178
+ "bisection", core_val, skill_val,
179
+ "bisection cross-check on [%g, %g]"
180
+ % (item.bracket[0], item.bracket[1])))
181
+ except Exception as e: # pragma: no cover
182
+ rows.append(_row_err("root-finding", "root_finding_logic.py",
183
+ "bisection", e))
184
+
185
+ # 6. convergence-verification: grid-study order + GCI
186
+ mod = _load_leaf("convergence-verification",
187
+ "convergence_verification_logic.py")
188
+ if mod is not None and hasattr(mod, "convergence_verdict"):
189
+ try:
190
+ meshes = model["convergence"]["meshes"]
191
+ f1, f2, f3 = (m["f"] for m in meshes)
192
+ r = model["convergence"]["refinement_ratio"]
193
+ core_cv = core.convergence_verdict(f1, f2, f3, r)
194
+ skill_cv = mod.convergence_verdict(f1, f2, f3, r)
195
+ if core_cv["order"] is not None and skill_cv["order"] is not None:
196
+ rows.append(_row("convergence-verification",
197
+ "convergence_verification_logic.py",
198
+ "convergence_verdict.order",
199
+ core_cv["order"], skill_cv["order"],
200
+ "observed order of the three-mesh study"))
201
+ rows.append(_row("convergence-verification",
202
+ "convergence_verification_logic.py",
203
+ "convergence_verdict.gci",
204
+ core_cv["gci"], skill_cv["gci"],
205
+ "grid convergence index of the study"))
206
+ rows.append(_row("convergence-verification",
207
+ "convergence_verification_logic.py",
208
+ "convergence_verdict.verdict",
209
+ 1.0 if core_cv["verdict"] == skill_cv["verdict"]
210
+ else 0.0,
211
+ 1.0,
212
+ "convergence verdict classification"))
213
+ except Exception as e: # pragma: no cover
214
+ rows.append(_row_err("convergence-verification",
215
+ "convergence_verification_logic.py",
216
+ "convergence_verdict", e))
217
+
218
+ # 7. singular-value-decomposition: condition number of the solve
219
+ mod = _load_leaf("singular-value-decomposition",
220
+ "singular_value_decomposition_logic.py")
221
+ if mod is not None and hasattr(mod, "condition_number"):
222
+ try:
223
+ sv = model["linear_solve"]["conditioning"]["singular_values"]
224
+ core_val = core.condition_number(sv)
225
+ skill_val = mod.condition_number(sv)
226
+ rows.append(_row("singular-value-decomposition",
227
+ "singular_value_decomposition_logic.py",
228
+ "condition_number", core_val, skill_val,
229
+ "2-norm condition number s_max/s_min of A"))
230
+ except Exception as e: # pragma: no cover
231
+ rows.append(_row_err("singular-value-decomposition",
232
+ "singular_value_decomposition_logic.py",
233
+ "condition_number", e))
234
+
235
+ # 8. matrix-operations: reference dense solve of A c = b
236
+ mod = _load_leaf("matrix-operations", "matrix_operations_logic.py")
237
+ if mod is not None and hasattr(mod, "solve"):
238
+ try:
239
+ core_val = core.solve(item.system_A, item.system_b)[0]
240
+ skill_val = mod.solve(item.system_A, item.system_b)[0]
241
+ rows.append(_row("matrix-operations", "matrix_operations_logic.py",
242
+ "solve", core_val, skill_val,
243
+ "first component of c = A^-1 b"))
244
+ except Exception as e: # pragma: no cover
245
+ rows.append(_row_err("matrix-operations",
246
+ "matrix_operations_logic.py", "solve", e))
247
+ return rows
248
+
249
+
250
+ def _provenance(rows):
251
+ ok_rows = [r for r in rows if r.get("dispatched") and not r.get("error")]
252
+ return {
253
+ "role": ROLE_SLUG,
254
+ "core": {
255
+ "file": "core/numerical_analysis_core.py",
256
+ "functions": ["trapezoid", "simpson",
257
+ "richardson_error_trapezoid", "newton_raphson",
258
+ "bisection", "convergence_verdict", "solve",
259
+ "condition_number", "build_report",
260
+ "check_report"],
261
+ "version": "0.1.0",
262
+ },
263
+ "skills": rows,
264
+ "cross_checked": bool(ok_rows) and all(r.get("agrees")
265
+ for r in ok_rows),
266
+ "disclaimer": "DRAFT for human review. Not an approval document.",
267
+ }
268
+
269
+
270
+ def _parse_csv_floats(text):
271
+ return [float(t) for t in str(text).split(",") if t.strip()]
272
+
273
+
274
+ def cmd_build(args):
275
+ item = core.example_item()
276
+ # allow re-verifying different code reports from the CLI
277
+ if args.code_integral is not None:
278
+ item.code_integral = args.code_integral
279
+ if args.code_integral_tol is not None:
280
+ item.code_integral_tol = args.code_integral_tol
281
+ if args.code_mach is not None:
282
+ item.code_mach = args.code_mach
283
+ if args.code_mach_tol is not None:
284
+ item.code_mach_tol = args.code_mach_tol
285
+ if args.code_solve:
286
+ vals = _parse_csv_floats(args.code_solve)
287
+ if len(vals) != len(item.system_b):
288
+ print("error: --code-solve needs %d comma-separated values"
289
+ % len(item.system_b))
290
+ return 1
291
+ item.code_solution = vals
292
+ if args.code_solve_tol is not None:
293
+ item.code_solve_tol = args.code_solve_tol
294
+
295
+ model = core.build_report(item)
296
+ md = core.render_report_markdown(model)
297
+
298
+ # program profile (customer tailoring): load + apply context header
299
+ profile = None
300
+ try:
301
+ profile = evidence.load_profile(args.profile)
302
+ except ValueError as e:
303
+ print(f"error: bad profile: {e}")
304
+ return 1
305
+ if profile:
306
+ model["profile"] = {
307
+ "customer": profile.get("customer"),
308
+ "program": profile.get("program"),
309
+ "basis": profile.get("basis"),
310
+ "authority": profile.get("authority"),
311
+ "der": profile.get("der"),
312
+ "document_prefix": profile.get("document_prefix"),
313
+ "revision": profile.get("revision"),
314
+ }
315
+ md = evidence.profile_header_block(profile) + md
316
+
317
+ gates = core.check_report(model)
318
+
319
+ rows = []
320
+ if not args.no_dispatch:
321
+ rows = _run_crosschecks(item, model)
322
+
323
+ if args.out:
324
+ with open(args.out, "w") as f:
325
+ f.write(md if md.endswith("\n") else md + "\n")
326
+ print("built Numerical Methods Verification Memo -> %s" % args.out)
327
+ for c in model["checks"]:
328
+ print(" %-12s verdict=%-4s discrepancy=%.6g (tol %g)"
329
+ % (c["operation"][:12], c["verdict"], c["discrepancy"],
330
+ c["code_tolerance"]))
331
+ conv = model["convergence"]
332
+ print(" grid study: p=%s gci=%s verdict=%s"
333
+ % (conv["observed_order"], conv["gci_fraction"],
334
+ conv["verdict"]))
335
+ print("gates: all_pass=%s %s" % (gates["all_pass"], gates))
336
+ if profile:
337
+ print("profile: %s / %s" % (profile.get("customer"),
338
+ profile.get("program")))
339
+ if args.bundle:
340
+ prov = _provenance(rows)
341
+ paths = evidence.write_bundle(
342
+ args.out, ROLE_SLUG,
343
+ "Numerical Methods Verification Memo", model, gates, prov)
344
+ print("bundle: model=%s" % paths["model"])
345
+ print(" gates=%s" % paths["gates"])
346
+ print(" provenance=%s" % paths["provenance"])
347
+ if rows:
348
+ for r in rows:
349
+ if r.get("dispatched") and not r.get("error"):
350
+ print("cross-check: %-32s core=%-12s skill=%-12s "
351
+ "delta=%s agrees=%s"
352
+ % (r["function"], r["core_value"],
353
+ r["skill_value"], r["delta"], r["agrees"]))
354
+ else:
355
+ print("cross-check: not dispatched (no AeroSkills logic)")
356
+ else:
357
+ print(md)
358
+ return 0 if gates["all_pass"] else 1
359
+
360
+
361
+ def cmd_check(args):
362
+ if not os.path.exists(args.file):
363
+ print(f"file not found: {args.file}")
364
+ return 1
365
+ md = open(args.file).read()
366
+ gates = core.check_report_markdown(md)
367
+ for name, ok in gates.items():
368
+ if name != "all_pass":
369
+ print(f"{'PASS' if ok else 'FAIL'} {name}")
370
+ print(f"RESULT: {'PASS' if gates['all_pass'] else 'FAIL'}")
371
+ return 0 if gates["all_pass"] else 1
372
+
373
+
374
+ def main():
375
+ p = argparse.ArgumentParser(
376
+ description="Numerical Analysis Engineer role: numerical methods "
377
+ "verification memo")
378
+ sub = p.add_subparsers(dest="cmd", required=True)
379
+
380
+ b = sub.add_parser("build")
381
+ b.add_argument("--out", default="",
382
+ help="output file (default: stdout)")
383
+ b.add_argument("--bundle", action="store_true",
384
+ help="emit evidence/{model,gates,provenance}.json")
385
+ b.add_argument("--profile", default="",
386
+ help="program profile JSON (customer tailoring, "
387
+ "docs/PROFILE-SCHEMA.md)")
388
+ b.add_argument("--no-dispatch", action="store_true",
389
+ help="skip dispatching bound AeroSkills logic")
390
+ b.add_argument("--code-integral", type=float, default=None,
391
+ help="code-reported integral (N) to re-verify")
392
+ b.add_argument("--code-integral-tol", type=float, default=None,
393
+ help="code-claimed integral tolerance (N)")
394
+ b.add_argument("--code-mach", type=float, default=None,
395
+ help="code-reported subsonic Mach root to re-verify")
396
+ b.add_argument("--code-mach-tol", type=float, default=None,
397
+ help="code-claimed root residual tolerance")
398
+ b.add_argument("--code-solve", default="",
399
+ help="code-reported solution as c0,c1 to re-verify")
400
+ b.add_argument("--code-solve-tol", type=float, default=None,
401
+ help="code-claimed solve component tolerance")
402
+ b.set_defaults(fn=cmd_build)
403
+
404
+ c = sub.add_parser("check")
405
+ c.add_argument("--file", required=True,
406
+ help="verification memo markdown to gate-check")
407
+ c.set_defaults(fn=cmd_check)
408
+
409
+ args = p.parse_args()
410
+ sys.exit(args.fn(args))
411
+
412
+
413
+ if __name__ == "__main__":
414
+ main()