@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.
Files changed (34) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +43 -2
  3. package/bin/aqaf.js +12 -0
  4. package/index.js +32 -0
  5. package/package.json +50 -4
  6. package/vendor/aqaf/__init__.py +2 -0
  7. package/vendor/aqaf/__main__.py +3 -0
  8. package/vendor/aqaf/cli.py +583 -0
  9. package/vendor/aqaf/data/catalog.json +562 -0
  10. package/vendor/aqaf/data/packs/automation.json +16 -0
  11. package/vendor/aqaf/data/packs/mobile.json +13 -0
  12. package/vendor/aqaf/data/packs/systems.json +17 -0
  13. package/vendor/aqaf/data/packs/web.json +16 -0
  14. package/vendor/aqaf/data/profiles/ai_pipeline.json +39 -0
  15. package/vendor/aqaf/data/profiles/android.json +34 -0
  16. package/vendor/aqaf/data/profiles/api_backend.json +44 -0
  17. package/vendor/aqaf/data/profiles/browser_extension.json +29 -0
  18. package/vendor/aqaf/data/profiles/cli_automation.json +34 -0
  19. package/vendor/aqaf/data/profiles/desktop_utility.json +39 -0
  20. package/vendor/aqaf/data/profiles/embedded_iot.json +34 -0
  21. package/vendor/aqaf/data/profiles/google_apps_script.json +34 -0
  22. package/vendor/aqaf/data/profiles/mcp_agent_server.json +34 -0
  23. package/vendor/aqaf/data/profiles/multirepo_system.json +29 -0
  24. package/vendor/aqaf/data/profiles/npm_library.json +39 -0
  25. package/vendor/aqaf/data/profiles/rust_app.json +34 -0
  26. package/vendor/aqaf/data/profiles/saas_or_web_app.json +44 -0
  27. package/vendor/aqaf/data/profiles/scraper_data_pipeline.json +39 -0
  28. package/vendor/aqaf/data/profiles/swift_app.json +34 -0
  29. package/vendor/aqaf/data/profiles/tauri_electron.json +34 -0
  30. package/vendor/aqaf/data/profiles/unknown_or_general_app.json +25 -0
  31. package/vendor/aqaf/data/profiles/windows_client_server.json +34 -0
  32. package/vendor/aqaf/data/profiles/wordpress_plugin.json +39 -0
  33. package/vendor/aqaf/data/source_registry.json +302 -0
  34. 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 ''