@aqaf/cli 0.0.0-stage → 1.1.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/LICENSE +21 -0
- package/README.md +43 -2
- package/bin/aqaf.js +12 -0
- package/index.js +32 -0
- package/package.json +50 -4
- package/vendor/aqaf/__init__.py +2 -0
- package/vendor/aqaf/__main__.py +3 -0
- package/vendor/aqaf/cli.py +583 -0
- package/vendor/aqaf/data/catalog.json +562 -0
- package/vendor/aqaf/data/packs/automation.json +16 -0
- package/vendor/aqaf/data/packs/mobile.json +13 -0
- package/vendor/aqaf/data/packs/systems.json +17 -0
- package/vendor/aqaf/data/packs/web.json +16 -0
- package/vendor/aqaf/data/profiles/ai_pipeline.json +39 -0
- package/vendor/aqaf/data/profiles/android.json +34 -0
- package/vendor/aqaf/data/profiles/api_backend.json +44 -0
- package/vendor/aqaf/data/profiles/browser_extension.json +29 -0
- package/vendor/aqaf/data/profiles/cli_automation.json +34 -0
- package/vendor/aqaf/data/profiles/desktop_utility.json +39 -0
- package/vendor/aqaf/data/profiles/embedded_iot.json +34 -0
- package/vendor/aqaf/data/profiles/google_apps_script.json +34 -0
- package/vendor/aqaf/data/profiles/mcp_agent_server.json +34 -0
- package/vendor/aqaf/data/profiles/multirepo_system.json +29 -0
- package/vendor/aqaf/data/profiles/npm_library.json +39 -0
- package/vendor/aqaf/data/profiles/rust_app.json +34 -0
- package/vendor/aqaf/data/profiles/saas_or_web_app.json +44 -0
- package/vendor/aqaf/data/profiles/scraper_data_pipeline.json +39 -0
- package/vendor/aqaf/data/profiles/swift_app.json +34 -0
- package/vendor/aqaf/data/profiles/tauri_electron.json +34 -0
- package/vendor/aqaf/data/profiles/unknown_or_general_app.json +25 -0
- package/vendor/aqaf/data/profiles/windows_client_server.json +34 -0
- package/vendor/aqaf/data/profiles/wordpress_plugin.json +39 -0
- package/vendor/aqaf/data/source_registry.json +302 -0
- package/vendor/aqaf/registry.py +124 -0
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "B01",
|
|
4
|
+
"type": "book",
|
|
5
|
+
"title": "Software Architecture in Practice, 4e",
|
|
6
|
+
"authors_or_owner": "Bass; Clements; Kazman",
|
|
7
|
+
"edition_or_date": "2021",
|
|
8
|
+
"url": "https://www.oreilly.com/library/view/software-architecture-in/9780136885979/",
|
|
9
|
+
"relevant_for": "Quality attributes; architecture evaluation; tactics",
|
|
10
|
+
"aqaf_independent_interpretation": true
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"id": "B02",
|
|
14
|
+
"type": "book",
|
|
15
|
+
"title": "Fundamentals of Software Architecture",
|
|
16
|
+
"authors_or_owner": "Richards; Ford",
|
|
17
|
+
"edition_or_date": "2020",
|
|
18
|
+
"url": "https://www.oreilly.com/library/view/fundamentals-of-software/9781492043447/",
|
|
19
|
+
"relevant_for": "Style trade-offs; architectural characteristics",
|
|
20
|
+
"aqaf_independent_interpretation": true
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"id": "B03",
|
|
24
|
+
"type": "book",
|
|
25
|
+
"title": "Building Evolutionary Architectures, 2e",
|
|
26
|
+
"authors_or_owner": "Ford; Parsons; Kua; Sadalage",
|
|
27
|
+
"edition_or_date": "2022",
|
|
28
|
+
"url": "https://www.oreilly.com/library/view/building-evolutionary-architectures/9781492097532/",
|
|
29
|
+
"relevant_for": "Fitness functions and incremental governance",
|
|
30
|
+
"aqaf_independent_interpretation": true
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"id": "B04",
|
|
34
|
+
"type": "book",
|
|
35
|
+
"title": "Designing Data-Intensive Applications",
|
|
36
|
+
"authors_or_owner": "Kleppmann",
|
|
37
|
+
"edition_or_date": "2017",
|
|
38
|
+
"url": "https://www.oreilly.com/library/view/designing-data-intensive-applications/9781491903063/",
|
|
39
|
+
"relevant_for": "Data consistency, storage, transactions, distributed systems",
|
|
40
|
+
"aqaf_independent_interpretation": true
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"id": "B05",
|
|
44
|
+
"type": "book",
|
|
45
|
+
"title": "Domain-Driven Design",
|
|
46
|
+
"authors_or_owner": "Evans",
|
|
47
|
+
"edition_or_date": "2003",
|
|
48
|
+
"url": "https://www.pearson.de/domain-driven-design-tackling-complexity-in-the-heart-of-software-9780321125217",
|
|
49
|
+
"relevant_for": "Bounded contexts and domain language",
|
|
50
|
+
"aqaf_independent_interpretation": true
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"id": "B06",
|
|
54
|
+
"type": "book",
|
|
55
|
+
"title": "Working Effectively with Legacy Code",
|
|
56
|
+
"authors_or_owner": "Feathers",
|
|
57
|
+
"edition_or_date": "2004",
|
|
58
|
+
"url": "https://www.oreilly.com/library/view/working-effectively-with/0131177052/",
|
|
59
|
+
"relevant_for": "Characterization tests and safe seams",
|
|
60
|
+
"aqaf_independent_interpretation": true
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"id": "B07",
|
|
64
|
+
"type": "book",
|
|
65
|
+
"title": "Accelerate",
|
|
66
|
+
"authors_or_owner": "Forsgren; Humble; Kim",
|
|
67
|
+
"edition_or_date": "2018",
|
|
68
|
+
"url": "https://dl.acm.org/doi/10.5555/3235404",
|
|
69
|
+
"relevant_for": "Measuring delivery capabilities; not a code architecture score",
|
|
70
|
+
"aqaf_independent_interpretation": true
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"id": "B08",
|
|
74
|
+
"type": "book",
|
|
75
|
+
"title": "Team Topologies, 2e",
|
|
76
|
+
"authors_or_owner": "Skelton; Pais",
|
|
77
|
+
"edition_or_date": "2025",
|
|
78
|
+
"url": "https://teamtopologies.com/books",
|
|
79
|
+
"relevant_for": "Architecture/team boundaries and cognitive load",
|
|
80
|
+
"aqaf_independent_interpretation": true
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"id": "B09",
|
|
84
|
+
"type": "book",
|
|
85
|
+
"title": "Clean Architecture",
|
|
86
|
+
"authors_or_owner": "Robert C. Martin",
|
|
87
|
+
"edition_or_date": "2017",
|
|
88
|
+
"url": "https://www.oreilly.com/library/view/clean-architecture-a/9780134494272/",
|
|
89
|
+
"relevant_for": "Dependency direction; one design approach, not universal law",
|
|
90
|
+
"aqaf_independent_interpretation": true
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"id": "B10",
|
|
94
|
+
"type": "book",
|
|
95
|
+
"title": "Monolith to Microservices",
|
|
96
|
+
"authors_or_owner": "Sam Newman",
|
|
97
|
+
"edition_or_date": "2019",
|
|
98
|
+
"url": "https://samnewman.io/books/monolith-to-microservices/",
|
|
99
|
+
"relevant_for": "Incremental extraction trade-offs",
|
|
100
|
+
"aqaf_independent_interpretation": true
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"id": "B11",
|
|
104
|
+
"type": "book",
|
|
105
|
+
"title": "Software Architecture: The Hard Parts",
|
|
106
|
+
"authors_or_owner": "Ford; Richards; Sadalage; Dehghani",
|
|
107
|
+
"edition_or_date": "2021",
|
|
108
|
+
"url": "https://www.oreilly.com/library/view/software-architecture-the/9781492086888/",
|
|
109
|
+
"relevant_for": "Trade-off analysis and distribution costs",
|
|
110
|
+
"aqaf_independent_interpretation": true
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
"id": "B12",
|
|
114
|
+
"type": "book",
|
|
115
|
+
"title": "Release It!, 2e",
|
|
116
|
+
"authors_or_owner": "Michael Nygard",
|
|
117
|
+
"edition_or_date": "2018",
|
|
118
|
+
"url": "https://www.pragmatic.org/titles/mnee2/release-it-second-edition/",
|
|
119
|
+
"relevant_for": "Runtime resilience, stability and production operations",
|
|
120
|
+
"aqaf_independent_interpretation": true
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"id": "B13",
|
|
124
|
+
"type": "book",
|
|
125
|
+
"title": "A Philosophy of Software Design, 2e",
|
|
126
|
+
"authors_or_owner": "John Ousterhout",
|
|
127
|
+
"edition_or_date": "2021",
|
|
128
|
+
"url": "https://www.web.stanford.edu/~ouster/cgi-bin/aposd.php",
|
|
129
|
+
"relevant_for": "Managing complexity; deep modules vs unnecessary layers",
|
|
130
|
+
"aqaf_independent_interpretation": true
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
"id": "B14",
|
|
134
|
+
"type": "book",
|
|
135
|
+
"title": "Patterns of Enterprise Application Architecture",
|
|
136
|
+
"authors_or_owner": "Martin Fowler",
|
|
137
|
+
"edition_or_date": "2002",
|
|
138
|
+
"url": "https://martinfowler.com/books/eaa.html",
|
|
139
|
+
"relevant_for": "Domain and data mapping patterns",
|
|
140
|
+
"aqaf_independent_interpretation": true
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
"id": "B15",
|
|
144
|
+
"type": "book",
|
|
145
|
+
"title": "Understanding Software Dynamics",
|
|
146
|
+
"authors_or_owner": "Richard L. Sites",
|
|
147
|
+
"edition_or_date": "2021",
|
|
148
|
+
"url": "https://www.oreilly.com/library/view/understanding-software-dynamics/9780137589692/",
|
|
149
|
+
"relevant_for": "Evidence-based performance measurement",
|
|
150
|
+
"aqaf_independent_interpretation": true
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
"id": "S01",
|
|
154
|
+
"type": "official",
|
|
155
|
+
"title": "Codex Agents and AGENTS.md",
|
|
156
|
+
"authors_or_owner": "OpenAI",
|
|
157
|
+
"edition_or_date": "accessed 2026-11-07",
|
|
158
|
+
"url": "https://developers.openai.com/codex/guides/agents-md",
|
|
159
|
+
"relevant_for": "Agent repository instructions",
|
|
160
|
+
"aqaf_independent_interpretation": true
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
"id": "S02",
|
|
164
|
+
"type": "official",
|
|
165
|
+
"title": "OpenAI Agent Skills",
|
|
166
|
+
"authors_or_owner": "OpenAI",
|
|
167
|
+
"edition_or_date": "accessed 2026-11-07",
|
|
168
|
+
"url": "https://developers.openai.com/api/docs/guides/tools-skills",
|
|
169
|
+
"relevant_for": "Skill structure and versioning",
|
|
170
|
+
"aqaf_independent_interpretation": true
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
"id": "S03",
|
|
174
|
+
"type": "official",
|
|
175
|
+
"title": "Codex skills and prompt guidance update",
|
|
176
|
+
"authors_or_owner": "OpenAI",
|
|
177
|
+
"edition_or_date": "2026",
|
|
178
|
+
"url": "https://developers.openai.com/blog/rethinking-skills-and-prompts-for-gpt-6-astra",
|
|
179
|
+
"relevant_for": "Keep instructions concise, contextual",
|
|
180
|
+
"aqaf_independent_interpretation": true
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
"id": "S04",
|
|
184
|
+
"type": "official",
|
|
185
|
+
"title": "GitHub Actions Marketplace publishing",
|
|
186
|
+
"authors_or_owner": "GitHub",
|
|
187
|
+
"edition_or_date": "accessed 2026-11-07",
|
|
188
|
+
"url": "https://docs.github.com/en/actions/how-tos/create-and-publish-actions/publish-in-github-marketplace",
|
|
189
|
+
"relevant_for": "Separate action repository and root metadata",
|
|
190
|
+
"aqaf_independent_interpretation": true
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
"id": "S05",
|
|
194
|
+
"type": "official",
|
|
195
|
+
"title": "GitHub SARIF 2.1.0 support",
|
|
196
|
+
"authors_or_owner": "GitHub",
|
|
197
|
+
"edition_or_date": "accessed 2026-11-07",
|
|
198
|
+
"url": "https://docs.github.com/en/code-security/concepts/code-scanning/sarif-files",
|
|
199
|
+
"relevant_for": "Static analysis interchange format",
|
|
200
|
+
"aqaf_independent_interpretation": true
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
"id": "S06",
|
|
204
|
+
"type": "standard",
|
|
205
|
+
"title": "OWASP ASVS 5.0.0",
|
|
206
|
+
"authors_or_owner": "OWASP",
|
|
207
|
+
"edition_or_date": "2025",
|
|
208
|
+
"url": "https://asvs.owasp.org/",
|
|
209
|
+
"relevant_for": "Security requirements for web apps, separate from architecture fitness",
|
|
210
|
+
"aqaf_independent_interpretation": true
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
"id": "S07",
|
|
214
|
+
"type": "standard",
|
|
215
|
+
"title": "OpenSSF Scorecard",
|
|
216
|
+
"authors_or_owner": "OpenSSF",
|
|
217
|
+
"edition_or_date": "accessed 2026-11-07",
|
|
218
|
+
"url": "https://openssf.org/projects/scorecard/",
|
|
219
|
+
"relevant_for": "Open-source supply-chain hygiene",
|
|
220
|
+
"aqaf_independent_interpretation": true
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
"id": "S08",
|
|
224
|
+
"type": "official",
|
|
225
|
+
"title": "Swift Package Manager",
|
|
226
|
+
"authors_or_owner": "Swift project",
|
|
227
|
+
"edition_or_date": "accessed 2026-11-07",
|
|
228
|
+
"url": "https://docs.swift.org/swiftpm/documentation/packagemanagerdocs/",
|
|
229
|
+
"relevant_for": "Swift modularity and packages",
|
|
230
|
+
"aqaf_independent_interpretation": true
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
"id": "S09",
|
|
234
|
+
"type": "official",
|
|
235
|
+
"title": "Swift API Design Guidelines",
|
|
236
|
+
"authors_or_owner": "Swift project",
|
|
237
|
+
"edition_or_date": "accessed 2026-11-07",
|
|
238
|
+
"url": "https://www.swift.org/documentation/api-design-guidelines/",
|
|
239
|
+
"relevant_for": "Swift API consistency",
|
|
240
|
+
"aqaf_independent_interpretation": true
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
"id": "S10",
|
|
244
|
+
"type": "official",
|
|
245
|
+
"title": "The Cargo Book: workspaces",
|
|
246
|
+
"authors_or_owner": "Rust project",
|
|
247
|
+
"edition_or_date": "accessed 2026-11-07",
|
|
248
|
+
"url": "https://doc.rust-lang.org/cargo/reference/workspaces.html",
|
|
249
|
+
"relevant_for": "Rust crate/workspace separation",
|
|
250
|
+
"aqaf_independent_interpretation": true
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
"id": "S11",
|
|
254
|
+
"type": "official",
|
|
255
|
+
"title": "Rust Clippy",
|
|
256
|
+
"authors_or_owner": "Rust project",
|
|
257
|
+
"edition_or_date": "accessed 2026-11-07",
|
|
258
|
+
"url": "https://doc.rust-lang.org/clippy/",
|
|
259
|
+
"relevant_for": "Rust lint rules",
|
|
260
|
+
"aqaf_independent_interpretation": true
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
"id": "S12",
|
|
264
|
+
"type": "community",
|
|
265
|
+
"title": "ArchUnit",
|
|
266
|
+
"authors_or_owner": "ArchUnit",
|
|
267
|
+
"edition_or_date": "accessed 2026-11-07",
|
|
268
|
+
"url": "https://www.archunit.org/",
|
|
269
|
+
"relevant_for": "JVM architecture rule testing",
|
|
270
|
+
"aqaf_independent_interpretation": true
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
"id": "S13",
|
|
274
|
+
"type": "community",
|
|
275
|
+
"title": "dependency-cruiser",
|
|
276
|
+
"authors_or_owner": "dependency-cruiser",
|
|
277
|
+
"edition_or_date": "accessed 2026-11-07",
|
|
278
|
+
"url": "https://github.com/sverweij/dependency-cruiser",
|
|
279
|
+
"relevant_for": "JS/TS module dependencies",
|
|
280
|
+
"aqaf_independent_interpretation": true
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
"id": "S14",
|
|
284
|
+
"type": "official",
|
|
285
|
+
"title": "Android modularization guidance",
|
|
286
|
+
"authors_or_owner": "Android Developers",
|
|
287
|
+
"edition_or_date": "accessed 2026-11-07",
|
|
288
|
+
"url": "https://developer.android.com/topic/modularization",
|
|
289
|
+
"relevant_for": "Android module design",
|
|
290
|
+
"aqaf_independent_interpretation": true
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
"id": "S15",
|
|
294
|
+
"type": "official",
|
|
295
|
+
"title": "WordPress Plugin Handbook",
|
|
296
|
+
"authors_or_owner": "WordPress",
|
|
297
|
+
"edition_or_date": "accessed 2026-11-07",
|
|
298
|
+
"url": "https://developer.wordpress.org/plugins/",
|
|
299
|
+
"relevant_for": "Plugin structure and hooks",
|
|
300
|
+
"aqaf_independent_interpretation": true
|
|
301
|
+
}
|
|
302
|
+
]
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"""Strictly data-only architecture profiles and packs. Does not import or execute extension code."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
import json
|
|
4
|
+
import re
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from importlib import resources
|
|
7
|
+
|
|
8
|
+
IDENT = re.compile(r'^[a-z][a-z0-9_]{1,63}$')
|
|
9
|
+
SIGNALS = {'file','extension','path','text','json_key'}
|
|
10
|
+
|
|
11
|
+
class ProfileError(ValueError):
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def validate(doc, category):
|
|
16
|
+
if not isinstance(doc, dict) or doc.get('schema_version') != 1 or not IDENT.fullmatch(str(doc.get('id',''))):
|
|
17
|
+
raise ProfileError('Expected schema_version=1 and lowercase snake_case id')
|
|
18
|
+
if category == 'profile':
|
|
19
|
+
for key in ('name','product_type','target_architecture','status'):
|
|
20
|
+
if not isinstance(doc.get(key),str) or not doc[key].strip():
|
|
21
|
+
raise ProfileError(f'Profile {doc["id"]}: missing {key}')
|
|
22
|
+
if not isinstance(doc.get('packs'),list) or not all(IDENT.fullmatch(str(x)) for x in doc['packs']):
|
|
23
|
+
raise ProfileError(f'Profile {doc["id"]}: invalid packs')
|
|
24
|
+
if not isinstance(doc.get('detection'),list): raise ProfileError('detection must be a list')
|
|
25
|
+
for item in doc['detection']:
|
|
26
|
+
if not isinstance(item,dict) or item.get('kind') not in SIGNALS or not isinstance(item.get('value'),str) or not 0 <= item.get('weight',-1) <= 150:
|
|
27
|
+
raise ProfileError(f'Invalid declarative detection in {doc["id"]}')
|
|
28
|
+
if len(item['value']) > 200 or '..' in item['value']:
|
|
29
|
+
raise ProfileError('Unsafe/oversized detection value')
|
|
30
|
+
elif category == 'pack':
|
|
31
|
+
if not isinstance(doc.get('name'),str) or not isinstance(doc.get('profiles'),list):
|
|
32
|
+
raise ProfileError(f'Pack {doc["id"]}: invalid fields')
|
|
33
|
+
if not all(IDENT.fullmatch(str(p)) for p in doc['profiles']):
|
|
34
|
+
raise ProfileError('Invalid profile ID in pack')
|
|
35
|
+
return doc
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def read_doc(path: Path, category):
|
|
39
|
+
if path.is_symlink() or not path.is_file() or path.stat().st_size > 65536:
|
|
40
|
+
raise ProfileError(f'Invalid or oversized manifest: {path}')
|
|
41
|
+
try: return validate(json.loads(path.read_text(encoding='utf-8')), category)
|
|
42
|
+
except (OSError, ValueError) as e: raise ProfileError(f'{path}: {e}') from e
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class Registry:
|
|
46
|
+
def __init__(self, extra_dirs=(), extra_pack_dirs=()):
|
|
47
|
+
self.profiles = {}
|
|
48
|
+
self.packs = {}
|
|
49
|
+
pkg = resources.files('aqaf').joinpath('data')
|
|
50
|
+
for category, name, store in [('profile','profiles',self.profiles),('pack','packs',self.packs)]:
|
|
51
|
+
for item in sorted(pkg.joinpath(name).iterdir(), key=lambda p:p.name):
|
|
52
|
+
if item.name.endswith('.json'):
|
|
53
|
+
value = validate(json.loads(item.read_text(encoding='utf-8')),category)
|
|
54
|
+
if value['id'] in store: raise ProfileError('Duplicate built-in ID')
|
|
55
|
+
store[value['id']] = value
|
|
56
|
+
for folder in extra_dirs:
|
|
57
|
+
path=Path(folder)
|
|
58
|
+
if not path.is_dir(): raise ProfileError(f'Profile directory not found: {path}')
|
|
59
|
+
for item in sorted(path.glob('*.json')):
|
|
60
|
+
value=read_doc(item,'profile')
|
|
61
|
+
if value['id'] in self.profiles:
|
|
62
|
+
raise ProfileError(f'External profile may not override built-in ID {value["id"]}')
|
|
63
|
+
self.profiles[value['id']] = value
|
|
64
|
+
for folder in extra_pack_dirs:
|
|
65
|
+
path=Path(folder)
|
|
66
|
+
if not path.is_dir(): raise ProfileError(f'Pack directory not found: {path}')
|
|
67
|
+
for item in sorted(path.glob('*.json')):
|
|
68
|
+
value=read_doc(item,'pack')
|
|
69
|
+
if value['id'] in self.packs:
|
|
70
|
+
raise ProfileError(f'External pack may not override built-in ID {value["id"]}')
|
|
71
|
+
self.packs[value['id']] = value
|
|
72
|
+
for pack in self.packs.values():
|
|
73
|
+
for p in pack['profiles']:
|
|
74
|
+
if p not in self.profiles: raise ProfileError(f'Pack {pack["id"]}: unknown profile {p}')
|
|
75
|
+
|
|
76
|
+
def get_profile(self, id):
|
|
77
|
+
if id not in self.profiles: raise ProfileError(f'Unknown product profile {id}; run aqaf profiles list')
|
|
78
|
+
return self.profiles[id]
|
|
79
|
+
|
|
80
|
+
def get_pack(self,id):
|
|
81
|
+
if id not in self.packs: raise ProfileError(f'Unknown pack {id}; run aqaf packs list')
|
|
82
|
+
return self.packs[id]
|
|
83
|
+
|
|
84
|
+
def detect(self, files, root, code, pack='auto'):
|
|
85
|
+
if pack!='auto': allowed=set(self.get_pack(pack)['profiles']) | {pid for pid,p in self.profiles.items() if pack in p['packs']}
|
|
86
|
+
else: allowed=set(self.profiles)
|
|
87
|
+
filenames={p.name.lower() for p in files}
|
|
88
|
+
extensions={p.suffix.lower() for p in code}
|
|
89
|
+
paths='\n'.join(str(p.relative_to(root)).replace('\\','/').lower() for p in files)
|
|
90
|
+
# Text signal sampling is bounded; this detector never executes target files.
|
|
91
|
+
snippets='\n'.join(_read_small(p)[:1500].lower() for p in code[:350])
|
|
92
|
+
candidates=[]
|
|
93
|
+
for id,profile in self.profiles.items():
|
|
94
|
+
if id not in allowed or id=='unknown_or_general_app': continue
|
|
95
|
+
evidence=[]; total=0
|
|
96
|
+
for s in profile['detection']:
|
|
97
|
+
kind=s['kind']; val=s['value']; weight=s['weight']; matched=False
|
|
98
|
+
if kind=='file': matched=val.lower() in filenames
|
|
99
|
+
elif kind=='extension': matched=val.lower() in extensions
|
|
100
|
+
elif kind=='path': matched=val.lower() in paths
|
|
101
|
+
elif kind=='text': matched=val.lower() in snippets
|
|
102
|
+
elif kind=='json_key':
|
|
103
|
+
file_name,key=val.split(':',1) if ':' in val else ('','')
|
|
104
|
+
for p in files:
|
|
105
|
+
if p.name.lower()==file_name.lower():
|
|
106
|
+
try:
|
|
107
|
+
d=json.loads(_read_small(p,65536)); matched= key in d if isinstance(d,dict) else False
|
|
108
|
+
except (ValueError,UnicodeError): pass
|
|
109
|
+
if matched: break
|
|
110
|
+
if matched: total+=weight; evidence.append(kind+':'+val)
|
|
111
|
+
if evidence: candidates.append((total,id,evidence))
|
|
112
|
+
if not candidates: return 'unknown_or_general_app',[], 'low', []
|
|
113
|
+
candidates.sort(key=lambda x:(-x[0],x[1]))
|
|
114
|
+
best=candidates[0]
|
|
115
|
+
# Don't treat weak, generic evidence as an authoritative classification.
|
|
116
|
+
confidence='medium' if best[0]>=60 and (len(candidates)==1 or best[0]-candidates[1][0]>=20) else 'low'
|
|
117
|
+
summary=[{'profile':id,'signal_score':score,'signals':evidence} for score,id,evidence in candidates[:5]]
|
|
118
|
+
return best[1], best[2],confidence,summary
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _read_small(p,limit=65536):
|
|
122
|
+
try:
|
|
123
|
+
with p.open('rb') as handle: return handle.read(limit).decode('utf-8','replace')
|
|
124
|
+
except OSError: return ''
|