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.
- package/NOTICE +18 -0
- package/README.md +49 -0
- package/bin/aero-agent-roles.js +127 -0
- package/lib/catalog.js +97 -0
- package/lib/install.js +48 -0
- package/lib/mcp.js +199 -0
- package/manifest.json +516 -0
- package/package.json +44 -0
- package/roles/aerodynamics-engineer/ROLE.md +153 -0
- package/roles/aerodynamics-engineer/SOURCES.md +12 -0
- package/roles/aerodynamics-engineer/cli.py +143 -0
- package/roles/aerodynamics-engineer/core/aerodynamics_core.py +1218 -0
- package/roles/aerodynamics-engineer/templates/aero-design-report-template.md +72 -0
- package/roles/aerodynamics-engineer/tests/test_aerodynamics_core.py +227 -0
- package/roles/aerodynamics-engineer/tests/test_role_aerodynamics.py +94 -0
- package/roles/aircraft-design-engineer/ROLE.md +138 -0
- package/roles/aircraft-design-engineer/SOURCES.md +12 -0
- package/roles/aircraft-design-engineer/cli.py +152 -0
- package/roles/aircraft-design-engineer/core/aircraft_design_core.py +1278 -0
- package/roles/aircraft-design-engineer/templates/concept-design-template.md +99 -0
- package/roles/aircraft-design-engineer/tests/test_aircraft_design_core.py +269 -0
- package/roles/aircraft-design-engineer/tests/test_role_aircraft_design_engineer.py +106 -0
- package/roles/airworthiness-compliance-engineer/ROLE.md +122 -0
- package/roles/airworthiness-compliance-engineer/SOURCES.md +16 -0
- package/roles/airworthiness-compliance-engineer/cli.py +146 -0
- package/roles/airworthiness-compliance-engineer/core/airworthiness_core.py +894 -0
- package/roles/airworthiness-compliance-engineer/templates/compliance-matrix-template.md +46 -0
- package/roles/airworthiness-compliance-engineer/tests/test_airworthiness_core.py +224 -0
- package/roles/airworthiness-compliance-engineer/tests/test_role_airworthiness.py +85 -0
- package/roles/as9100-quality-auditor/ROLE.md +128 -0
- package/roles/as9100-quality-auditor/SOURCES.md +16 -0
- package/roles/as9100-quality-auditor/cli.py +136 -0
- package/roles/as9100-quality-auditor/core/as9100_core.py +857 -0
- package/roles/as9100-quality-auditor/templates/findings-report-template.md +119 -0
- package/roles/as9100-quality-auditor/tests/test_as9100_cli_profile.py +105 -0
- package/roles/as9100-quality-auditor/tests/test_as9100_core.py +204 -0
- package/roles/as9100-quality-auditor/tests/test_role_as9100.py +90 -0
- package/roles/data-bus-avionics-engineer/ROLE.md +146 -0
- package/roles/data-bus-avionics-engineer/SOURCES.md +23 -0
- package/roles/data-bus-avionics-engineer/cli.py +311 -0
- package/roles/data-bus-avionics-engineer/core/data_bus_avionics_core.py +1121 -0
- package/roles/data-bus-avionics-engineer/templates/bus-assessment-template.md +134 -0
- package/roles/data-bus-avionics-engineer/tests/test_bundle_protocol.py +137 -0
- package/roles/data-bus-avionics-engineer/tests/test_data_bus_avionics_core.py +261 -0
- package/roles/data-bus-avionics-engineer/tests/test_role_data_bus_avionics_engineer.py +121 -0
- package/roles/do160-environmental-engineer/ROLE.md +136 -0
- package/roles/do160-environmental-engineer/SOURCES.md +30 -0
- package/roles/do160-environmental-engineer/cli.py +147 -0
- package/roles/do160-environmental-engineer/core/do160_environmental_core.py +1246 -0
- package/roles/do160-environmental-engineer/templates/environmental-qualification-plan-template.md +114 -0
- package/roles/do160-environmental-engineer/tests/test_do160_environmental_core.py +312 -0
- package/roles/do160-environmental-engineer/tests/test_role_do160_environmental_engineer.py +281 -0
- package/roles/do178c-cert-engineer/ROLE.md +120 -0
- package/roles/do178c-cert-engineer/SOURCES.md +16 -0
- package/roles/do178c-cert-engineer/cli.py +129 -0
- package/roles/do178c-cert-engineer/core/do178c_core.py +411 -0
- package/roles/do178c-cert-engineer/templates/psac-template.md +74 -0
- package/roles/do178c-cert-engineer/tests/test_do178c_cert_engineer_core.py +91 -0
- package/roles/do178c-cert-engineer/tests/test_role_do178c.py +109 -0
- package/roles/do254-hardware-engineer/ROLE.md +127 -0
- package/roles/do254-hardware-engineer/SOURCES.md +19 -0
- package/roles/do254-hardware-engineer/cli.py +308 -0
- package/roles/do254-hardware-engineer/core/do254_hardware_core.py +761 -0
- package/roles/do254-hardware-engineer/templates/phac-template.md +87 -0
- package/roles/do254-hardware-engineer/tests/test_do254_bundle_profile.py +152 -0
- package/roles/do254-hardware-engineer/tests/test_do254_hardware_core.py +200 -0
- package/roles/do254-hardware-engineer/tests/test_role_do254_hardware.py +108 -0
- package/roles/engineering-analysis-engineer/ROLE.md +154 -0
- package/roles/engineering-analysis-engineer/SOURCES.md +15 -0
- package/roles/engineering-analysis-engineer/cli.py +151 -0
- package/roles/engineering-analysis-engineer/core/engineering_analysis_engineer_core.py +866 -0
- package/roles/engineering-analysis-engineer/templates/analysis-memo-template.md +73 -0
- package/roles/engineering-analysis-engineer/tests/test_engineering_analysis_engineer_core.py +367 -0
- package/roles/engineering-analysis-engineer/tests/test_role_engineering_analysis_engineer.py +113 -0
- package/roles/flight-management-engineer/ROLE.md +160 -0
- package/roles/flight-management-engineer/SOURCES.md +30 -0
- package/roles/flight-management-engineer/cli.py +258 -0
- package/roles/flight-management-engineer/core/flight_management_core.py +1473 -0
- package/roles/flight-management-engineer/templates/route-assessment-template.md +94 -0
- package/roles/flight-management-engineer/tests/test_flight_management_core.py +437 -0
- package/roles/flight-management-engineer/tests/test_role_flight_management_engineer.py +123 -0
- package/roles/flight-mechanics-engineer/ROLE.md +162 -0
- package/roles/flight-mechanics-engineer/SOURCES.md +51 -0
- package/roles/flight-mechanics-engineer/cli.py +149 -0
- package/roles/flight-mechanics-engineer/core/flight_mechanics_core.py +1507 -0
- package/roles/flight-mechanics-engineer/templates/perf-sc-report-template.md +82 -0
- package/roles/flight-mechanics-engineer/tests/test_flight_mechanics_core.py +277 -0
- package/roles/flight-mechanics-engineer/tests/test_role_flight_mechanics_engineer.py +141 -0
- package/roles/flight-test-engineer/ROLE.md +137 -0
- package/roles/flight-test-engineer/SOURCES.md +24 -0
- package/roles/flight-test-engineer/cli.py +135 -0
- package/roles/flight-test-engineer/core/flight_test_core.py +931 -0
- package/roles/flight-test-engineer/templates/flight-test-template.md +143 -0
- package/roles/flight-test-engineer/tests/test_flight_test_engineer_core.py +227 -0
- package/roles/flight-test-engineer/tests/test_role_flight_test_engineer.py +150 -0
- package/roles/flight-test-performance-engineer/ROLE.md +165 -0
- package/roles/flight-test-performance-engineer/SOURCES.md +20 -0
- package/roles/flight-test-performance-engineer/cli.py +319 -0
- package/roles/flight-test-performance-engineer/core/flight_test_performance_core.py +1398 -0
- package/roles/flight-test-performance-engineer/templates/performance-report-template.md +105 -0
- package/roles/flight-test-performance-engineer/tests/test_flight_test_performance_engineer_core.py +308 -0
- package/roles/flight-test-performance-engineer/tests/test_role_flight_test_performance_engineer.py +165 -0
- package/roles/gnc-engineer/ROLE.md +141 -0
- package/roles/gnc-engineer/SOURCES.md +35 -0
- package/roles/gnc-engineer/cli.py +141 -0
- package/roles/gnc-engineer/core/gnc_core.py +1074 -0
- package/roles/gnc-engineer/templates/gnc-report-template.md +105 -0
- package/roles/gnc-engineer/tests/test_gnc_engineer_core.py +238 -0
- package/roles/gnc-engineer/tests/test_role_gnc_engineer.py +142 -0
- package/roles/guidance-engineer/ROLE.md +156 -0
- package/roles/guidance-engineer/SOURCES.md +43 -0
- package/roles/guidance-engineer/cli.py +280 -0
- package/roles/guidance-engineer/core/guidance_core.py +820 -0
- package/roles/guidance-engineer/templates/guidance-report-template.md +70 -0
- package/roles/guidance-engineer/tests/test_guidance_engineer_core.py +269 -0
- package/roles/guidance-engineer/tests/test_role_guidance_engineer.py +146 -0
- package/roles/high-speed-aerodynamics-engineer/ROLE.md +161 -0
- package/roles/high-speed-aerodynamics-engineer/SOURCES.md +18 -0
- package/roles/high-speed-aerodynamics-engineer/cli.py +282 -0
- package/roles/high-speed-aerodynamics-engineer/core/high_speed_aero_core.py +1566 -0
- package/roles/high-speed-aerodynamics-engineer/templates/high-speed-memo-template.md +75 -0
- package/roles/high-speed-aerodynamics-engineer/tests/test_bundle_protocol.py +103 -0
- package/roles/high-speed-aerodynamics-engineer/tests/test_high_speed_aero_core.py +280 -0
- package/roles/high-speed-aerodynamics-engineer/tests/test_role_high_speed_aerodynamics.py +125 -0
- package/roles/ndt-engineer/ROLE.md +157 -0
- package/roles/ndt-engineer/SOURCES.md +18 -0
- package/roles/ndt-engineer/cli.py +316 -0
- package/roles/ndt-engineer/core/ndt_core.py +1590 -0
- package/roles/ndt-engineer/templates/ndt-plan-template.md +109 -0
- package/roles/ndt-engineer/tests/test_ndt_cli_bundle.py +183 -0
- package/roles/ndt-engineer/tests/test_ndt_core.py +437 -0
- package/roles/ndt-engineer/tests/test_role_ndt_engineer.py +148 -0
- package/roles/numerical-analysis-engineer/ROLE.md +158 -0
- package/roles/numerical-analysis-engineer/SOURCES.md +19 -0
- package/roles/numerical-analysis-engineer/cli.py +414 -0
- package/roles/numerical-analysis-engineer/core/numerical_analysis_core.py +1220 -0
- package/roles/numerical-analysis-engineer/templates/verification-memo-template.md +120 -0
- package/roles/numerical-analysis-engineer/tests/test_bundle_protocol.py +157 -0
- package/roles/numerical-analysis-engineer/tests/test_numerical_analysis_core.py +337 -0
- package/roles/numerical-analysis-engineer/tests/test_role_numerical_analysis_engineer.py +117 -0
- package/roles/propulsion-engineer/ROLE.md +163 -0
- package/roles/propulsion-engineer/SOURCES.md +15 -0
- package/roles/propulsion-engineer/cli.py +168 -0
- package/roles/propulsion-engineer/core/propulsion_core.py +1291 -0
- package/roles/propulsion-engineer/templates/propulsion-report-template.md +92 -0
- package/roles/propulsion-engineer/tests/test_propulsion_engineer_core.py +294 -0
- package/roles/propulsion-engineer/tests/test_role_propulsion_engineer.py +106 -0
- package/roles/space-systems-engineer/ROLE.md +161 -0
- package/roles/space-systems-engineer/SOURCES.md +49 -0
- package/roles/space-systems-engineer/cli.py +173 -0
- package/roles/space-systems-engineer/core/space_systems_core.py +980 -0
- package/roles/space-systems-engineer/templates/space-systems-template.md +88 -0
- package/roles/space-systems-engineer/tests/test_role_space_systems_engineer.py +117 -0
- package/roles/space-systems-engineer/tests/test_space_systems_engineer_core.py +247 -0
- package/roles/state-estimation-engineer/ROLE.md +154 -0
- package/roles/state-estimation-engineer/SOURCES.md +27 -0
- package/roles/state-estimation-engineer/cli.py +326 -0
- package/roles/state-estimation-engineer/core/state_estimation_core.py +958 -0
- package/roles/state-estimation-engineer/templates/nav-state-estimator-design-template.md +76 -0
- package/roles/state-estimation-engineer/tests/test_bundle_protocol.py +110 -0
- package/roles/state-estimation-engineer/tests/test_role_state_estimation_engineer.py +127 -0
- package/roles/state-estimation-engineer/tests/test_state_estimation_engineer_core.py +231 -0
- package/roles/structures-loads-engineer/ROLE.md +171 -0
- package/roles/structures-loads-engineer/SOURCES.md +44 -0
- package/roles/structures-loads-engineer/cli.py +219 -0
- package/roles/structures-loads-engineer/core/structures_loads_core.py +1112 -0
- package/roles/structures-loads-engineer/templates/loads-strength-report-template.md +96 -0
- package/roles/structures-loads-engineer/tests/test_bundle_protocol.py +101 -0
- package/roles/structures-loads-engineer/tests/test_role_structures_loads_engineer.py +147 -0
- package/roles/structures-loads-engineer/tests/test_structures_loads_core.py +261 -0
package/NOTICE
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
Aero Agent Roles
|
|
2
|
+
Copyright 2026 Ashforde OÜ
|
|
3
|
+
|
|
4
|
+
This product includes software and methodology developed by Ashforde OÜ
|
|
5
|
+
(Estonia). Licensed under the Apache License, Version 2.0 (the
|
|
6
|
+
"License"); you may not use this product except in compliance with the
|
|
7
|
+
License. You may obtain a copy of the License at
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0.
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
13
|
+
implied. See the License for the specific language governing
|
|
14
|
+
permissions and limitations under the License.
|
|
15
|
+
|
|
16
|
+
Aero Agent Roles builds on Aero Agent Skills (Apache-2.0). Standards
|
|
17
|
+
referenced within roles remain the property of their respective
|
|
18
|
+
publishers; no proprietary content is reproduced.
|
package/README.md
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# aero-agent-roles
|
|
2
|
+
|
|
3
|
+
**The role layer for aerospace engineering agents** — professional roles that bind verified [Aero Agent Skills](https://github.com/ashfordeOU/aero-agent-skills) into end-to-end deliverables, with evidence gates and a human sign-off stop, shipped as an npm CLI and an MCP server. Built and maintained by [Ashforde OÜ](https://ashforde.org).
|
|
4
|
+
|
|
5
|
+
> **Not yet published.** This package is wired and tested (`make package-test`) but `private: true` in `package.json` — npm refuses to publish it until that is deliberately removed. Ready to release, not released.
|
|
6
|
+
|
|
7
|
+
Full role bank, figures, and provenance: [github.com/ashfordeOU/aero-agent-roles](https://github.com/ashfordeOU/aero-agent-roles) · [ashforde.org/aeroagentroles](https://ashforde.org/aeroagentroles)
|
|
8
|
+
|
|
9
|
+
Each role is gated by a replayable offline battery (structure lint, offline behavior tests, no-verbatim sweep, security tripwire, manifest-matches-tree). This package bundles the tree at the released commit; live counts come from `aero-roles list`, never from this README.
|
|
10
|
+
|
|
11
|
+
## CLI
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm i -g aero-agent-roles # or: npx aero-agent-roles <command>
|
|
15
|
+
|
|
16
|
+
aero-roles list # domains, roles, skills bound
|
|
17
|
+
aero-roles search "DO-178C certification plan"
|
|
18
|
+
aero-roles show do178c-cert-engineer
|
|
19
|
+
aero-roles install do178c-cert-engineer --dest ./roles
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
`install` copies a role's full folder (`ROLE.md`, `templates/`, `tests/`, `core/`, `cli.py`) to `--dest <dir>` (`--link` symlinks instead of copying). A role is **not** an agent-router skill — `ROLE.md` has no trigger `description` the way `SKILL.md` does — so `install` does not register a role with any host's skill router; it hands you the files to run directly (`python3 <dest>/<slug>/cli.py build --bundle`) or to point an agent at by path.
|
|
23
|
+
|
|
24
|
+
## MCP server
|
|
25
|
+
|
|
26
|
+
Works in any Model Context Protocol host: JetBrains AI Assistant and Junie, Claude Desktop, Claude Code, VS Code, Cursor, Windsurf, Gemini CLI.
|
|
27
|
+
|
|
28
|
+
```json
|
|
29
|
+
{
|
|
30
|
+
"mcpServers": {
|
|
31
|
+
"aero-agent-roles": {
|
|
32
|
+
"command": "npx",
|
|
33
|
+
"args": ["-y", "aero-agent-roles", "mcp"]
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Tools: `search_roles` (title/deliverable/domain match), `get_role` (full `ROLE.md`), `list_domains`, `list_roles`, `get_standards` (the machine-readable standards register: publisher, gated flag, which roles bind it).
|
|
40
|
+
|
|
41
|
+
## What a role is
|
|
42
|
+
|
|
43
|
+
A folder with a `ROLE.md`: YAML frontmatter (identity, deliverable type, bound standards + skills), a body the agent follows (ordered workflow stages, an evidence gate per stage, forbidden actions, the human sign-off stop), an executable domain engine (`core/*.py` + `cli.py`) that computes the real deliverable offline, and an offline behavior-contract test.
|
|
44
|
+
|
|
45
|
+
Every role ends at the same line: **the human sign-off**. Roles produce draft deliverables with evidence. They never issue certification approval, declare compliance findings, or claim regulatory sign-off.
|
|
46
|
+
|
|
47
|
+
## License
|
|
48
|
+
|
|
49
|
+
Apache-2.0 © Ashforde OÜ (Estonia). Not affiliated with or endorsed by RTCA, EUROCAE, SAE International, IAQG, EASA, FAA, or any government.
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// aero-agent-roles CLI: list / search / show / install / where / mcp.
|
|
3
|
+
// Everything answers from the bundled tree + generated manifest — offline,
|
|
4
|
+
// deterministic, no telemetry, no network.
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
const { Catalog, version } = require('../lib/catalog');
|
|
8
|
+
const { install } = require('../lib/install');
|
|
9
|
+
|
|
10
|
+
const HELP = `aero-agent-roles ${version()} — the role layer for aerospace engineering agents (Ashforde OU)
|
|
11
|
+
|
|
12
|
+
Usage:
|
|
13
|
+
aero-roles list [domain] browse domains, roles
|
|
14
|
+
aero-roles search <job words...> match roles by title/deliverable/domain
|
|
15
|
+
aero-roles show <role-slug> print one ROLE.md
|
|
16
|
+
aero-roles install <role-slug...> --dest <dir>
|
|
17
|
+
--dest <dir> required: destination directory
|
|
18
|
+
--link symlink instead of copy
|
|
19
|
+
selector: all | <role-slug> (default: all)
|
|
20
|
+
aero-roles mcp run the MCP server on stdio
|
|
21
|
+
aero-roles where print the bundled roles root
|
|
22
|
+
aero-roles version
|
|
23
|
+
|
|
24
|
+
MCP host config (JetBrains AI Assistant/Junie, Claude Desktop, VS Code, Cursor, Windsurf):
|
|
25
|
+
{"mcpServers":{"aero-agent-roles":{"command":"npx","args":["-y","aero-agent-roles","mcp"]}}}
|
|
26
|
+
|
|
27
|
+
Note: a role is not an agent-router skill (ROLE.md has no trigger
|
|
28
|
+
description) — install copies role folders for direct/CLI use, it does
|
|
29
|
+
not register them with any host's skill router.
|
|
30
|
+
`;
|
|
31
|
+
|
|
32
|
+
function parseFlags(argv) {
|
|
33
|
+
const flags = {};
|
|
34
|
+
const rest = [];
|
|
35
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
36
|
+
if (argv[i] === '--dest') flags.dest = argv[++i];
|
|
37
|
+
else if (argv[i] === '--link') flags.link = true;
|
|
38
|
+
else rest.push(argv[i]);
|
|
39
|
+
}
|
|
40
|
+
return { flags, rest };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function cmdList(catalog, domain) {
|
|
44
|
+
if (!domain) {
|
|
45
|
+
const doms = new Map();
|
|
46
|
+
for (const r of catalog.roles) {
|
|
47
|
+
const d = doms.get(r.domain) || { roles: 0, skills_bound: 0 };
|
|
48
|
+
d.roles += 1;
|
|
49
|
+
d.skills_bound += r.skills_bound;
|
|
50
|
+
doms.set(r.domain, d);
|
|
51
|
+
}
|
|
52
|
+
for (const [name, d] of [...doms.entries()].sort()) {
|
|
53
|
+
console.log(`${name.padEnd(28)} ${String(d.roles).padStart(3)} roles ${String(d.skills_bound).padStart(4)} skills bound`);
|
|
54
|
+
}
|
|
55
|
+
console.log(`\n${catalog.manifest.counts.roles} roles, ${catalog.manifest.counts.domains} domains, ${catalog.manifest.counts.skills_bound} skills bound. \`list <domain>\` to drill in.`);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const hits = catalog.roles.filter((r) => r.domain === domain);
|
|
59
|
+
if (hits.length === 0) {
|
|
60
|
+
console.error(`nothing under '${domain}'. Run \`aero-roles list\` for domains.`);
|
|
61
|
+
process.exitCode = 1;
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
for (const r of hits) console.log(`${r.slug}: ${r.title} — ${r.deliverable_type}`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function main() {
|
|
68
|
+
const [cmd, ...args] = process.argv.slice(2);
|
|
69
|
+
if (!cmd || cmd === 'help' || cmd === '--help' || cmd === '-h') {
|
|
70
|
+
console.log(HELP);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (cmd === 'version' || cmd === '--version' || cmd === '-v') {
|
|
74
|
+
console.log(version());
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (cmd === 'mcp') {
|
|
78
|
+
require('../lib/mcp').serve();
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const catalog = new Catalog();
|
|
83
|
+
if (cmd === 'list') {
|
|
84
|
+
cmdList(catalog, args[0]);
|
|
85
|
+
} else if (cmd === 'search') {
|
|
86
|
+
const query = args.join(' ').trim();
|
|
87
|
+
if (!query) {
|
|
88
|
+
console.error('usage: aero-roles search <job words...>');
|
|
89
|
+
process.exitCode = 1;
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const hits = catalog.search(query, 5);
|
|
93
|
+
if (hits.length === 0) {
|
|
94
|
+
console.log('no role matches. Run `aero-roles list` to browse.');
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
for (const [i, h] of hits.entries()) {
|
|
98
|
+
console.log(`${i + 1}. ${h.role.slug} (score ${h.score})\n ${h.role.title} — ${h.role.deliverable_type}`);
|
|
99
|
+
}
|
|
100
|
+
} else if (cmd === 'show') {
|
|
101
|
+
const role = catalog.find(args[0] || '');
|
|
102
|
+
if (!role) {
|
|
103
|
+
console.error(`no role '${args[0] || ''}'. Try \`aero-roles search\`.`);
|
|
104
|
+
process.exitCode = 1;
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
process.stdout.write(catalog.readRoleMd(role.slug));
|
|
108
|
+
} else if (cmd === 'install') {
|
|
109
|
+
const { flags, rest } = parseFlags(args);
|
|
110
|
+
try {
|
|
111
|
+
const result = install(catalog, rest, flags);
|
|
112
|
+
for (const item of result.installed) console.log(`${result.linked ? 'linked' : 'installed'} ${item.slug} -> ${item.folder}/`);
|
|
113
|
+
console.log(`\n${result.installed.length} roles ${result.linked ? 'linked' : 'installed'} into ${result.dest}`);
|
|
114
|
+
} catch (e) {
|
|
115
|
+
console.error(e.message);
|
|
116
|
+
process.exitCode = 1;
|
|
117
|
+
}
|
|
118
|
+
} else if (cmd === 'where') {
|
|
119
|
+
console.log(catalog.root);
|
|
120
|
+
} else {
|
|
121
|
+
console.error(`unknown command '${cmd}'\n`);
|
|
122
|
+
console.log(HELP);
|
|
123
|
+
process.exitCode = 1;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
main();
|
package/lib/catalog.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// Catalog loader. Answers list/show/search from the bundled manifest.json
|
|
2
|
+
// + roles/ tree — offline, deterministic, no telemetry, no network.
|
|
3
|
+
//
|
|
4
|
+
// Roles are adopted deliberately, not routed by a trigger description the
|
|
5
|
+
// way Aero Agent Skills' SKILL.md files are (ROLE.md has no `description`
|
|
6
|
+
// field) — search() here is a plain token-overlap match over title,
|
|
7
|
+
// deliverable_type, and domain, useful for "what role fits this job"
|
|
8
|
+
// browsing, not a claimed parity port of any evaluated router.
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const fs = require('node:fs');
|
|
12
|
+
const path = require('node:path');
|
|
13
|
+
|
|
14
|
+
const STOP = new Set([
|
|
15
|
+
'a', 'an', 'the', 'for', 'or', 'and', 'of', 'to', 'in', 'on', 'with',
|
|
16
|
+
'is', 'are', 'was', 'be', 'at', 'by', 'from', 'as', 'into', 'onto',
|
|
17
|
+
]);
|
|
18
|
+
|
|
19
|
+
const TOKEN_RE = /[a-z0-9][a-z0-9-]*/g;
|
|
20
|
+
|
|
21
|
+
function tokens(text) {
|
|
22
|
+
const out = [];
|
|
23
|
+
for (const m of String(text).toLowerCase().matchAll(TOKEN_RE)) {
|
|
24
|
+
if (!STOP.has(m[0])) out.push(m[0]);
|
|
25
|
+
}
|
|
26
|
+
return out;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const PKG_ROOT = path.join(__dirname, '..');
|
|
30
|
+
|
|
31
|
+
function rolesRoot() {
|
|
32
|
+
// Prefer the repo checkout so a stale prepack payload copy can never
|
|
33
|
+
// shadow the live tree in development; installed packages have no
|
|
34
|
+
// ../../roles and fall through to the bundled copy.
|
|
35
|
+
const repo = path.join(PKG_ROOT, '..', '..', 'roles');
|
|
36
|
+
if (fs.existsSync(repo) && fs.existsSync(path.join(PKG_ROOT, '..', '..', 'manifest.json'))) return repo;
|
|
37
|
+
const bundled = path.join(PKG_ROOT, 'roles');
|
|
38
|
+
if (fs.existsSync(bundled)) return bundled;
|
|
39
|
+
throw new Error('roles tree not found (looked for the repo checkout and a bundled roles/)');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function loadManifest() {
|
|
43
|
+
return JSON.parse(fs.readFileSync(path.join(PKG_ROOT, 'manifest.json'), 'utf8'));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function version() {
|
|
47
|
+
return JSON.parse(fs.readFileSync(path.join(PKG_ROOT, 'package.json'), 'utf8')).version;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
class Catalog {
|
|
51
|
+
constructor() {
|
|
52
|
+
this.root = rolesRoot();
|
|
53
|
+
this.manifest = loadManifest();
|
|
54
|
+
this._tok = new Map();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
get roles() {
|
|
58
|
+
return this.manifest.roles;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
find(slug) {
|
|
62
|
+
return this.manifest.roles.find((r) => r.slug === slug);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
readRoleMd(slug) {
|
|
66
|
+
return fs.readFileSync(path.join(this.root, slug, 'ROLE.md'), 'utf8');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
_tokensFor(role) {
|
|
70
|
+
let t = this._tok.get(role.slug);
|
|
71
|
+
if (!t) {
|
|
72
|
+
t = {
|
|
73
|
+
haystack: new Set(tokens(`${role.title} ${role.deliverable_type} ${role.domain} ${role.slug}`)),
|
|
74
|
+
};
|
|
75
|
+
this._tok.set(role.slug, t);
|
|
76
|
+
}
|
|
77
|
+
return t;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
score(role, query) {
|
|
81
|
+
const q = new Set(tokens(query));
|
|
82
|
+
if (q.size === 0) return 0;
|
|
83
|
+
const t = this._tokensFor(role);
|
|
84
|
+
let s = 0;
|
|
85
|
+
for (const w of q) if (t.haystack.has(w)) s += 1;
|
|
86
|
+
return s;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
search(query, limit = 5) {
|
|
90
|
+
const scored = this.manifest.roles.map((r) => ({ role: r, score: this.score(r, query) }));
|
|
91
|
+
scored.sort((a, b) => b.score - a.score
|
|
92
|
+
|| (a.role.slug < b.role.slug ? -1 : a.role.slug > b.role.slug ? 1 : 0));
|
|
93
|
+
return scored.filter((s) => s.score > 0).slice(0, limit);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
module.exports = { Catalog, tokens, version, PKG_ROOT };
|
package/lib/install.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Installer: copies one role's full folder (ROLE.md, templates/, tests/,
|
|
2
|
+
// core/, cli.py, SOURCES.md) to a destination directory.
|
|
3
|
+
//
|
|
4
|
+
// Unlike Aero Agent Skills' installer, this does NOT claim any agent-host
|
|
5
|
+
// skill-router integration: ROLE.md has no `description` trigger field, so
|
|
6
|
+
// dropping it into a host's skills/ directory would not make it
|
|
7
|
+
// discoverable by that host's router — it would just be an inert folder.
|
|
8
|
+
// A role is adopted deliberately (a human or agent runs its cli.py
|
|
9
|
+
// directly), so `install` is a plain "get me this role's files" copy, not
|
|
10
|
+
// a harness-aware install. `--dest` is required for exactly that reason.
|
|
11
|
+
'use strict';
|
|
12
|
+
|
|
13
|
+
const fs = require('node:fs');
|
|
14
|
+
const path = require('node:path');
|
|
15
|
+
|
|
16
|
+
function install(catalog, slugs, opts) {
|
|
17
|
+
if (!opts.dest) {
|
|
18
|
+
throw new Error("install requires --dest <dir> (roles are not agent-router "
|
|
19
|
+
+ "skills, so there is no default harness skills root to guess)");
|
|
20
|
+
}
|
|
21
|
+
const dest = path.resolve(opts.dest);
|
|
22
|
+
const selection = slugs.length === 0 || slugs.includes('all')
|
|
23
|
+
? catalog.roles.slice()
|
|
24
|
+
: slugs.map((slug) => {
|
|
25
|
+
const r = catalog.find(slug);
|
|
26
|
+
if (!r) {
|
|
27
|
+
const near = catalog.search(slug.replace(/-/g, ' '), 3).map((s) => s.role.slug).join(', ');
|
|
28
|
+
throw new Error(`no role '${slug}'. Closest: ${near || 'none'}. Run \`aero-roles list\`.`);
|
|
29
|
+
}
|
|
30
|
+
return r;
|
|
31
|
+
});
|
|
32
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
33
|
+
const installed = [];
|
|
34
|
+
for (const r of selection) {
|
|
35
|
+
const src = path.join(catalog.root, r.slug);
|
|
36
|
+
const dst = path.join(dest, r.slug);
|
|
37
|
+
if (opts.link) {
|
|
38
|
+
fs.rmSync(dst, { recursive: true, force: true });
|
|
39
|
+
fs.symlinkSync(src, dst, 'dir');
|
|
40
|
+
} else {
|
|
41
|
+
fs.cpSync(src, dst, { recursive: true, force: true });
|
|
42
|
+
}
|
|
43
|
+
installed.push({ slug: r.slug, folder: r.slug });
|
|
44
|
+
}
|
|
45
|
+
return { dest, installed, linked: !!opts.link };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
module.exports = { install };
|
package/lib/mcp.js
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
// MCP server over stdio: newline-delimited JSON-RPC 2.0, zero dependencies.
|
|
2
|
+
// Same wire protocol and host-config shape as Aero Agent Skills' MCP
|
|
3
|
+
// server (JetBrains AI Assistant/Junie, Claude Desktop, VS Code, Cursor,
|
|
4
|
+
// Windsurf all speak this identically) — implements initialize, ping,
|
|
5
|
+
// tools/list, tools/call; anything else gets -32601, and requests without
|
|
6
|
+
// an id are notifications and never answered. Offline: every answer comes
|
|
7
|
+
// from the bundled manifest — no network, ever.
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
const { Catalog, version } = require('./catalog');
|
|
11
|
+
|
|
12
|
+
const TOOLS = [
|
|
13
|
+
{
|
|
14
|
+
name: 'search_roles',
|
|
15
|
+
description: 'Search Aero Agent Roles by plain-text match over title, deliverable type, and '
|
|
16
|
+
+ 'domain. Use when a task needs a role that owns an end-to-end aerospace-engineering '
|
|
17
|
+
+ 'deliverable (a certification plan, a compliance matrix, an analysis report), not just a '
|
|
18
|
+
+ 'single skill. Returns ranked role slugs; load the winner with get_role.',
|
|
19
|
+
inputSchema: {
|
|
20
|
+
type: 'object',
|
|
21
|
+
properties: {
|
|
22
|
+
query: { type: 'string', description: 'The deliverable or job, in plain words.' },
|
|
23
|
+
limit: { type: 'integer', minimum: 1, maximum: 25, description: 'Max results (default 5).' },
|
|
24
|
+
},
|
|
25
|
+
required: ['query'],
|
|
26
|
+
additionalProperties: false,
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: 'get_role',
|
|
31
|
+
description: 'Fetch one complete ROLE.md by slug (e.g. do178c-cert-engineer). Use after '
|
|
32
|
+
+ 'search_roles or list_roles to load the full contract: the ordered workflow stages, the '
|
|
33
|
+
+ 'evidence gate per stage, the bound skills and standards, forbidden actions, and the human '
|
|
34
|
+
+ 'sign-off stop.',
|
|
35
|
+
inputSchema: {
|
|
36
|
+
type: 'object',
|
|
37
|
+
properties: {
|
|
38
|
+
slug: { type: 'string', description: 'Role slug as returned by search_roles or list_roles.' },
|
|
39
|
+
},
|
|
40
|
+
required: ['slug'],
|
|
41
|
+
additionalProperties: false,
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: 'list_domains',
|
|
46
|
+
description: 'List the engineering domains with their role and skills-bound counts. Use to get '
|
|
47
|
+
+ 'an overview of what the role bank covers before searching or browsing.',
|
|
48
|
+
inputSchema: { type: 'object', properties: {}, additionalProperties: false },
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: 'list_roles',
|
|
52
|
+
description: 'List roles, optionally filtered to one domain. Use to browse a discipline; each '
|
|
53
|
+
+ 'entry gives the slug to pass to get_role.',
|
|
54
|
+
inputSchema: {
|
|
55
|
+
type: 'object',
|
|
56
|
+
properties: {
|
|
57
|
+
domain: { type: 'string', description: 'Domain, e.g. avionics.' },
|
|
58
|
+
},
|
|
59
|
+
additionalProperties: false,
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: 'get_standards',
|
|
64
|
+
description: 'Look up the machine-readable aerospace standards register: publisher, whether '
|
|
65
|
+
+ 'verbatim text is gated, and which roles bind it. Use when a task cites a standard '
|
|
66
|
+
+ '(DO-178C, ARP4754A, AS9100, CS-25, ECSS, ...) and you need its scope or handling rules. '
|
|
67
|
+
+ 'Omit id to list all.',
|
|
68
|
+
inputSchema: {
|
|
69
|
+
type: 'object',
|
|
70
|
+
properties: {
|
|
71
|
+
id: { type: 'string', description: 'Standard id, e.g. do-178c (case-insensitive).' },
|
|
72
|
+
},
|
|
73
|
+
additionalProperties: false,
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
function text(t) {
|
|
79
|
+
return { content: [{ type: 'text', text: t }], isError: false };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function toolError(t) {
|
|
83
|
+
return { content: [{ type: 'text', text: t }], isError: true };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function domainsSummary(catalog) {
|
|
87
|
+
const doms = new Map();
|
|
88
|
+
for (const r of catalog.roles) {
|
|
89
|
+
const d = doms.get(r.domain) || { roles: 0, skills_bound: 0 };
|
|
90
|
+
d.roles += 1;
|
|
91
|
+
d.skills_bound += r.skills_bound;
|
|
92
|
+
doms.set(r.domain, d);
|
|
93
|
+
}
|
|
94
|
+
return [...doms.entries()].sort()
|
|
95
|
+
.map(([name, d]) => `${name}: ${d.roles} roles, ${d.skills_bound} skills bound`)
|
|
96
|
+
.join('\n');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function callTool(catalog, name, args) {
|
|
100
|
+
args = args || {};
|
|
101
|
+
switch (name) {
|
|
102
|
+
case 'search_roles': {
|
|
103
|
+
if (!args.query || !String(args.query).trim()) return toolError('query is required');
|
|
104
|
+
const limit = Math.min(Math.max(args.limit || 5, 1), 25);
|
|
105
|
+
const hits = catalog.search(String(args.query), limit);
|
|
106
|
+
if (hits.length === 0) return toolError('no role matches that query. Call list_domains or list_roles to browse.');
|
|
107
|
+
return text(hits.map((h, i) => `${i + 1}. ${h.role.slug} (score ${h.score})\n ${h.role.title} — ${h.role.deliverable_type}`).join('\n\n'));
|
|
108
|
+
}
|
|
109
|
+
case 'get_role': {
|
|
110
|
+
const role = catalog.find(String(args.slug || ''));
|
|
111
|
+
if (!role) {
|
|
112
|
+
const near = catalog.search(String(args.slug || '').replace(/-/g, ' '), 3)
|
|
113
|
+
.map((h) => h.role.slug).join(', ');
|
|
114
|
+
return toolError(`no role '${args.slug}'. Closest: ${near || 'none'}.`);
|
|
115
|
+
}
|
|
116
|
+
return text(catalog.readRoleMd(role.slug));
|
|
117
|
+
}
|
|
118
|
+
case 'list_domains':
|
|
119
|
+
return text(domainsSummary(catalog));
|
|
120
|
+
case 'list_roles': {
|
|
121
|
+
let roles = catalog.roles;
|
|
122
|
+
if (args.domain) roles = roles.filter((r) => r.domain === args.domain);
|
|
123
|
+
if (roles.length === 0) return toolError('no roles match that domain filter. Call list_domains first.');
|
|
124
|
+
return text(roles.map((r) => `${r.slug}: ${r.title} — ${r.deliverable_type}`).join('\n'));
|
|
125
|
+
}
|
|
126
|
+
case 'get_standards': {
|
|
127
|
+
let standards = catalog.manifest.standards;
|
|
128
|
+
if (args.id) {
|
|
129
|
+
const id = String(args.id).toLowerCase();
|
|
130
|
+
standards = standards.filter((s) => s.id.toLowerCase() === id);
|
|
131
|
+
if (standards.length === 0) return toolError(`no standard with id '${args.id}'. Omit id to list all.`);
|
|
132
|
+
}
|
|
133
|
+
return text(standards.map((s) =>
|
|
134
|
+
`${s.id}: ${s.name}\n publisher: ${s.publisher}${s.gated ? ' (gated: no verbatim text)' : ''}\n roles: ${s.roles.join(', ')}`)
|
|
135
|
+
.join('\n\n'));
|
|
136
|
+
}
|
|
137
|
+
default:
|
|
138
|
+
return toolError(`unknown tool '${name}'`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function serve() {
|
|
143
|
+
const catalog = new Catalog();
|
|
144
|
+
let buffer = '';
|
|
145
|
+
|
|
146
|
+
const reply = (id, result, error) => {
|
|
147
|
+
const msg = { jsonrpc: '2.0', id };
|
|
148
|
+
if (error) msg.error = error;
|
|
149
|
+
else msg.result = result;
|
|
150
|
+
process.stdout.write(JSON.stringify(msg) + '\n');
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const handle = (req) => {
|
|
154
|
+
const isNotification = req.id === undefined || req.id === null;
|
|
155
|
+
switch (req.method) {
|
|
156
|
+
case 'initialize':
|
|
157
|
+
return reply(req.id, {
|
|
158
|
+
protocolVersion: (req.params && req.params.protocolVersion) || '2025-06-18',
|
|
159
|
+
capabilities: { tools: { listChanged: false } },
|
|
160
|
+
serverInfo: { name: 'aero-agent-roles', version: version() },
|
|
161
|
+
});
|
|
162
|
+
case 'ping':
|
|
163
|
+
return reply(req.id, {});
|
|
164
|
+
case 'tools/list':
|
|
165
|
+
return reply(req.id, { tools: TOOLS });
|
|
166
|
+
case 'tools/call':
|
|
167
|
+
try {
|
|
168
|
+
return reply(req.id, callTool(catalog, req.params && req.params.name, req.params && req.params.arguments));
|
|
169
|
+
} catch (e) {
|
|
170
|
+
return reply(req.id, toolError(`tool failed: ${e.message}`));
|
|
171
|
+
}
|
|
172
|
+
default:
|
|
173
|
+
if (isNotification) return undefined;
|
|
174
|
+
return reply(req.id, undefined, { code: -32601, message: `method not found: ${req.method}` });
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
process.stdin.setEncoding('utf8');
|
|
179
|
+
process.stdin.on('data', (chunk) => {
|
|
180
|
+
buffer += chunk;
|
|
181
|
+
let nl;
|
|
182
|
+
while ((nl = buffer.indexOf('\n')) !== -1) {
|
|
183
|
+
const line = buffer.slice(0, nl).trim();
|
|
184
|
+
buffer = buffer.slice(nl + 1);
|
|
185
|
+
if (!line) continue;
|
|
186
|
+
let req;
|
|
187
|
+
try {
|
|
188
|
+
req = JSON.parse(line);
|
|
189
|
+
} catch {
|
|
190
|
+
reply(null, undefined, { code: -32700, message: 'parse error' });
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
handle(req);
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
process.stdin.on('end', () => process.exit(0));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
module.exports = { serve, callTool, TOOLS };
|