great-cto 2.77.0 → 2.77.1

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/board/.claude-plugin/plugin.json +256 -0
  2. package/board/packages/board/lib/alerts.mjs +413 -0
  3. package/board/packages/board/lib/beads.mjs +233 -0
  4. package/board/packages/board/lib/config.mjs +45 -0
  5. package/board/packages/board/lib/data-readers.mjs +340 -0
  6. package/board/packages/board/lib/fleet.mjs +363 -0
  7. package/board/packages/board/lib/metrics.mjs +282 -0
  8. package/board/packages/board/lib/notifications.mjs +50 -0
  9. package/board/packages/board/lib/projects.mjs +256 -0
  10. package/board/packages/board/lib/routes.mjs +1036 -0
  11. package/board/packages/board/lib/share.mjs +143 -0
  12. package/board/packages/board/lib/sse.mjs +20 -0
  13. package/board/packages/board/lib/state.mjs +31 -0
  14. package/board/packages/board/lib/util.mjs +36 -0
  15. package/board/packages/board/lib/verdicts.mjs +168 -0
  16. package/board/packages/board/lib/watchers.mjs +87 -0
  17. package/board/packages/board/mcp-server.mjs +310 -0
  18. package/board/packages/board/public/assets/apple-touch-icon.png +0 -0
  19. package/board/packages/board/public/assets/favicon-16.png +0 -0
  20. package/board/packages/board/public/assets/favicon-32.png +0 -0
  21. package/board/packages/board/public/assets/favicon.ico +0 -0
  22. package/board/packages/board/public/assets/favicon.svg +8 -0
  23. package/board/packages/board/public/index.html +5452 -0
  24. package/board/packages/board/public/share.html +1190 -0
  25. package/board/packages/board/public/sw.js +79 -0
  26. package/board/packages/board/push-adapter.mjs +222 -0
  27. package/board/packages/board/server.mjs +133 -0
  28. package/board/packages/cli/dist/archetypes.js +1461 -0
  29. package/board/scripts/lib/change-tier.mjs +119 -0
  30. package/board/scripts/lib/gate-plan.mjs +119 -0
  31. package/board/scripts/lib/judge-model.mjs +42 -0
  32. package/dist/board-path.js +47 -0
  33. package/dist/main.js +3 -31
  34. package/package.json +3 -1
@@ -0,0 +1,1461 @@
1
+ // Archetype decision: detected stack → archetype recommendation.
2
+ // Mirrors great_cto's 25 archetypes (+ greenfield fallback) in skills/great_cto/ARCHETYPES.md.
3
+ // Rules are evaluated; highest score wins.
4
+ const RULES = [
5
+ // ── browser-extension ─────────────────────────────
6
+ // High priority: explicit MV3 manifest signal beats web-service / library
7
+ {
8
+ archetype: "browser-extension",
9
+ score: (d) => (d.stack.includes("browser-extension") ? 8 : 0),
10
+ reason: (_d) => "manifest.json with manifest_version detected — Chrome/Firefox/Edge extension",
11
+ },
12
+ // ── game ─────────────────────────────────────────
13
+ // High priority: Unity / Unreal / Godot signals beat library
14
+ {
15
+ archetype: "game",
16
+ score: (d) => {
17
+ let s = 0;
18
+ if (d.stack.includes("unity"))
19
+ s += 8;
20
+ if (d.stack.includes("unreal"))
21
+ s += 8;
22
+ if (d.stack.includes("godot"))
23
+ s += 8;
24
+ if (d.stack.includes("phaser"))
25
+ s += 6;
26
+ if (d.stack.includes("cocos"))
27
+ s += 6;
28
+ return s;
29
+ },
30
+ reason: (d) => {
31
+ const bits = [];
32
+ if (d.stack.includes("unity"))
33
+ bits.push("Unity");
34
+ if (d.stack.includes("unreal"))
35
+ bits.push("Unreal");
36
+ if (d.stack.includes("godot"))
37
+ bits.push("Godot");
38
+ if (d.stack.includes("phaser"))
39
+ bits.push("Phaser");
40
+ if (d.stack.includes("cocos"))
41
+ bits.push("Cocos");
42
+ return `game engine detected: ${bits.join(", ")} — netcode/anti-cheat/age-rating gates`;
43
+ },
44
+ },
45
+ // ── devtools ─────────────────────────────────────
46
+ // High priority: OpenAPI + multi-language SDK presence beats library
47
+ {
48
+ archetype: "devtools",
49
+ score: (d) => {
50
+ let s = 0;
51
+ if (d.stack.includes("devtools-api"))
52
+ s += 7;
53
+ if (d.stack.includes("openapi-spec"))
54
+ s += 8;
55
+ if (d.stack.includes("graphql-schema"))
56
+ s += 6;
57
+ if (d.stack.includes("multi-sdk"))
58
+ s += 4;
59
+ if (d.stack.includes("stainless"))
60
+ s += 4;
61
+ if (d.stack.includes("docs-platform"))
62
+ s += 2;
63
+ return s;
64
+ },
65
+ reason: (d) => {
66
+ const bits = [];
67
+ if (d.stack.includes("openapi-spec"))
68
+ bits.push("OpenAPI spec");
69
+ if (d.stack.includes("graphql-schema"))
70
+ bits.push("GraphQL schema");
71
+ if (d.stack.includes("multi-sdk"))
72
+ bits.push("multi-language SDKs");
73
+ if (d.stack.includes("stainless"))
74
+ bits.push("Stainless");
75
+ return `developer-tools platform detected (${bits.join(", ")}) — API stability + SDK quality gates`;
76
+ },
77
+ },
78
+ // ── commerce ─────────────────────────────────────
79
+ {
80
+ archetype: "commerce",
81
+ score: (d) => {
82
+ let s = 0;
83
+ // Don't score commerce if fintech signals are stronger (Plaid etc.)
84
+ if (d.stack.includes("plaid") || d.stack.includes("dwolla") || d.stack.includes("teller"))
85
+ return 0;
86
+ if (d.stack.includes("stripe"))
87
+ s += 7;
88
+ if (d.stack.includes("shopify"))
89
+ s += 7;
90
+ if (d.stack.includes("braintree"))
91
+ s += 6;
92
+ if (d.stack.includes("adyen"))
93
+ s += 6;
94
+ if (d.stack.includes("paddle"))
95
+ s += 5;
96
+ if (d.stack.includes("lemonsqueezy"))
97
+ s += 5;
98
+ // Add small bonus when paired with web framework (real e-commerce, not just SDK)
99
+ if (s > 0 && (d.stack.includes("next.js") || d.stack.includes("nuxt") ||
100
+ d.stack.includes("remix") || d.stack.includes("sveltekit")))
101
+ s += 1;
102
+ return s;
103
+ },
104
+ reason: (d) => {
105
+ const payments = [];
106
+ if (d.stack.includes("stripe"))
107
+ payments.push("Stripe");
108
+ if (d.stack.includes("shopify"))
109
+ payments.push("Shopify");
110
+ if (d.stack.includes("braintree"))
111
+ payments.push("Braintree");
112
+ if (d.stack.includes("adyen"))
113
+ payments.push("Adyen");
114
+ if (d.stack.includes("paddle"))
115
+ payments.push("Paddle");
116
+ return `payments SDK detected: ${payments.join(", ")} — PCI-DSS gate mandatory`;
117
+ },
118
+ },
119
+ // ── web3 ─────────────────────────────────────────
120
+ {
121
+ archetype: "web3",
122
+ score: (d) => {
123
+ let s = 0;
124
+ if (d.stack.includes("solidity"))
125
+ s += 6;
126
+ if (d.stack.includes("web3"))
127
+ s += 4;
128
+ return s;
129
+ },
130
+ reason: (_d) => "Solidity / smart-contract tooling detected — formal verification gate",
131
+ },
132
+ // ── iot-embedded ─────────────────────────────────
133
+ {
134
+ archetype: "iot-embedded",
135
+ score: (d) => (d.stack.includes("embedded") ? 6 : 0),
136
+ reason: (_d) => "platformio.ini / sdkconfig detected — embedded firmware archetype",
137
+ },
138
+ // ── agent-product (LLM + vector DB OR multi-agent framework) ────
139
+ // Higher score than ai-system: this is an autonomous-agent product, not a wrapper
140
+ {
141
+ archetype: "agent-product",
142
+ score: (d) => {
143
+ const llms = ["anthropic-sdk", "openai-sdk", "google-ai", "aws-bedrock", "cohere"];
144
+ const llmFrameworks = ["langchain", "llamaindex"]; // RAG-capable LLM frameworks
145
+ const vdbs = ["pinecone", "weaviate", "chroma", "qdrant"];
146
+ const agents = ["langgraph", "crewai", "autogen", "mastra", "mcp"];
147
+ const hasLlm = llms.some((s) => d.stack.includes(s));
148
+ const hasLlmFw = llmFrameworks.some((s) => d.stack.includes(s));
149
+ const hasVdb = vdbs.some((s) => d.stack.includes(s));
150
+ const hasAgentFw = agents.some((s) => d.stack.includes(s));
151
+ let s = 0;
152
+ // RAG-style agent: any LLM (raw SDK or framework) + vector DB
153
+ if ((hasLlm || hasLlmFw) && hasVdb)
154
+ s += 7;
155
+ if (hasAgentFw)
156
+ s += 6; // explicit agent framework
157
+ if ((hasLlm || hasLlmFw) && hasAgentFw)
158
+ s += 2; // bonus
159
+ // Project-shape signals (agent-runtime archetypes that don't depend
160
+ // on a specific framework — e.g. a deterministic local agent runtime
161
+ // built from scratch). Each signal is weak; multiple together strong.
162
+ const kws = d.readmeKeywords.map((k) => k.toLowerCase());
163
+ const agentRuntimeKws = [
164
+ "agent", "agent-runtime", "agent_runtime", "agentic",
165
+ "tool-calling", "tool-use", "tool_use", "tool-registry",
166
+ "planner", "agent-loop", "agent-runtime", "deterministic-agent",
167
+ "plan-step", "planstep", "tool-allowlist", "agent-budget",
168
+ "mcp-server", "mcp-client", "mcp",
169
+ ];
170
+ const matchedAgentKws = agentRuntimeKws.filter((k) => kws.includes(k));
171
+ if (matchedAgentKws.length >= 3)
172
+ s += 5; // strong shape signal
173
+ else if (matchedAgentKws.length === 2)
174
+ s += 3;
175
+ else if (matchedAgentKws.length === 1)
176
+ s += 1;
177
+ // Stack-name patterns: agent-runtime / agent-product (the user
178
+ // explicitly named their library/package after agent or runtime).
179
+ // Detected via signals — d.stack includes the package name as a token.
180
+ const stackJoined = d.stack.join(" ").toLowerCase();
181
+ if (/(?:^| )agent[-_](?:runtime|product|loop|kit|sdk)/.test(stackJoined))
182
+ s += 2;
183
+ // Voice-AI agent: telephony provider + STT + TTS + LLM = autonomous voice agent
184
+ const voiceProviders = ["twilio", "vonage", "livekit"];
185
+ const sttProviders = ["deepgram"];
186
+ const ttsProviders = ["elevenlabs", "hume"];
187
+ const hasVoice = voiceProviders.some((s) => d.stack.includes(s));
188
+ const hasStt = sttProviders.some((s) => d.stack.includes(s));
189
+ const hasTts = ttsProviders.some((s) => d.stack.includes(s));
190
+ if (hasVoice && hasLlm && (hasStt || hasTts))
191
+ s += 7; // strong: full voice-agent stack
192
+ else if (hasVoice && hasLlm)
193
+ s += 4; // medium: voice + LLM
194
+ return s;
195
+ },
196
+ reason: (d) => {
197
+ const bits = [];
198
+ if (d.stack.includes("langgraph"))
199
+ bits.push("LangGraph");
200
+ if (d.stack.includes("crewai"))
201
+ bits.push("CrewAI");
202
+ if (d.stack.includes("autogen"))
203
+ bits.push("AutoGen");
204
+ if (d.stack.includes("mastra"))
205
+ bits.push("Mastra");
206
+ if (d.stack.includes("mcp"))
207
+ bits.push("MCP SDK");
208
+ const vdb = ["pinecone", "weaviate", "chroma", "qdrant"].filter((s) => d.stack.includes(s));
209
+ if (vdb.length)
210
+ bits.push(`vector DB (${vdb.join(",")})`);
211
+ const voice = ["twilio", "vonage", "livekit", "deepgram", "elevenlabs", "hume"].filter((s) => d.stack.includes(s));
212
+ if (voice.length)
213
+ bits.push(`voice stack (${voice.join(",")})`);
214
+ return `agent-product detected — ${bits.join(", ") || "agent signals"} — agent-eval + isolation + prompt-injection gates required`;
215
+ },
216
+ },
217
+ // ── ai-system ────────────────────────────────────
218
+ // Lower score than agent-product: an LLM-using app without vector DB / agent FW
219
+ {
220
+ archetype: "ai-system",
221
+ score: (d) => {
222
+ let s = 0;
223
+ if (d.stack.includes("anthropic-sdk"))
224
+ s += 6;
225
+ if (d.stack.includes("openai-sdk"))
226
+ s += 5;
227
+ if (d.stack.includes("google-ai"))
228
+ s += 5;
229
+ if (d.stack.includes("aws-bedrock"))
230
+ s += 5;
231
+ if (d.stack.includes("cohere"))
232
+ s += 4;
233
+ if (d.stack.includes("replicate"))
234
+ s += 4;
235
+ if (d.stack.includes("langchain"))
236
+ s += 5;
237
+ if (d.stack.includes("llamaindex"))
238
+ s += 5;
239
+ if (d.stack.includes("vercel-ai-sdk"))
240
+ s += 5;
241
+ if (d.stack.includes("ml"))
242
+ s += 2;
243
+ // Combo bonus: using multiple AI providers/frameworks together
244
+ const aiCount = ["anthropic-sdk", "openai-sdk", "google-ai", "aws-bedrock", "cohere", "replicate", "langchain", "llamaindex", "vercel-ai-sdk"]
245
+ .filter(x => d.stack.includes(x)).length;
246
+ if (aiCount >= 2)
247
+ s += 3;
248
+ // Don't double-score if already an agent-product
249
+ const agents = ["langgraph", "crewai", "autogen", "mastra", "mcp"];
250
+ if (agents.some((a) => d.stack.includes(a)))
251
+ s = Math.max(0, s - 2);
252
+ const vdbs = ["pinecone", "weaviate", "chroma", "qdrant"];
253
+ const hasVdb = vdbs.some((v) => d.stack.includes(v));
254
+ if (hasVdb)
255
+ s = Math.max(0, s - 2);
256
+ // Strong RAG signal — LangChain/LlamaIndex + VDB is almost certainly
257
+ // an agent-product (RAG-style), not a generic ai-system. The +5 score
258
+ // these frameworks contribute (above) is appropriate when there's no
259
+ // VDB; with a VDB it overcounts. Apply an additional deduction so
260
+ // agent-product wins the tie. See test "LangChain + Pinecone → agent-product".
261
+ const hasLlmFw = ["langchain", "llamaindex"].some((s) => d.stack.includes(s));
262
+ if (hasVdb && hasLlmFw)
263
+ s = Math.max(0, s - 5);
264
+ return s;
265
+ },
266
+ reason: (d) => {
267
+ const bits = [];
268
+ if (d.stack.includes("anthropic-sdk"))
269
+ bits.push("Anthropic SDK");
270
+ if (d.stack.includes("openai-sdk"))
271
+ bits.push("OpenAI SDK");
272
+ if (d.stack.includes("google-ai"))
273
+ bits.push("Gemini");
274
+ if (d.stack.includes("aws-bedrock"))
275
+ bits.push("AWS Bedrock");
276
+ if (d.stack.includes("langchain"))
277
+ bits.push("LangChain");
278
+ if (d.stack.includes("llamaindex"))
279
+ bits.push("LlamaIndex");
280
+ if (d.stack.includes("vercel-ai-sdk"))
281
+ bits.push("Vercel AI SDK");
282
+ if (d.stack.includes("ml"))
283
+ bits.push("ML stack");
284
+ return `AI/LLM tooling detected (${bits.join(", ")}) — prompt injection + output sanitization gates`;
285
+ },
286
+ },
287
+ // ── fintech (Plaid, banking integrations) ────────
288
+ {
289
+ archetype: "fintech",
290
+ score: (d) => {
291
+ let s = 0;
292
+ if (d.stack.includes("plaid"))
293
+ s += 10;
294
+ if (d.stack.includes("wise"))
295
+ s += 9;
296
+ if (d.stack.includes("dwolla"))
297
+ s += 9;
298
+ if (d.stack.includes("teller"))
299
+ s += 9;
300
+ if (d.stack.includes("fintech"))
301
+ s += 7;
302
+ // Emerging-markets payment providers
303
+ if (d.stack.includes("razorpay"))
304
+ s += 9;
305
+ if (d.stack.includes("paystack"))
306
+ s += 9;
307
+ if (d.stack.includes("flutterwave"))
308
+ s += 9;
309
+ if (d.stack.includes("mercadopago"))
310
+ s += 9;
311
+ if (d.readmeKeywords.includes("fintech"))
312
+ s += 2;
313
+ return s;
314
+ },
315
+ reason: (d) => {
316
+ const bits = [];
317
+ if (d.stack.includes("plaid"))
318
+ bits.push("Plaid");
319
+ if (d.stack.includes("wise"))
320
+ bits.push("Wise");
321
+ if (d.stack.includes("dwolla"))
322
+ bits.push("Dwolla");
323
+ if (d.stack.includes("teller"))
324
+ bits.push("Teller");
325
+ if (d.stack.includes("razorpay"))
326
+ bits.push("Razorpay (India)");
327
+ if (d.stack.includes("paystack"))
328
+ bits.push("Paystack (Nigeria)");
329
+ if (d.stack.includes("flutterwave"))
330
+ bits.push("Flutterwave (Africa)");
331
+ if (d.stack.includes("mercadopago"))
332
+ bits.push("MercadoPago (LATAM)");
333
+ return `fintech integration: ${bits.join(", ")} — SOX, PCI, KYC/AML compliance gates`;
334
+ },
335
+ },
336
+ // ── marketplace (two-sided platform — Stripe Connect / KYC) ────
337
+ // Stronger than commerce when payouts to sellers + KYC providers detected
338
+ {
339
+ archetype: "marketplace",
340
+ score: (d) => {
341
+ let s = 0;
342
+ if (d.stack.includes("stripe-connect"))
343
+ s += 9;
344
+ if (d.stack.includes("adyen-marketpay"))
345
+ s += 9;
346
+ if (d.stack.includes("persona"))
347
+ s += 5;
348
+ if (d.stack.includes("onfido"))
349
+ s += 5;
350
+ if (d.stack.includes("sumsub"))
351
+ s += 5;
352
+ if (d.readmeKeywords.includes("marketplace") || d.readmeKeywords.includes("two-sided"))
353
+ s += 4;
354
+ if (d.readmeKeywords.includes("seller") && d.readmeKeywords.includes("buyer"))
355
+ s += 3;
356
+ // Don't score if pure single-merchant commerce
357
+ if (s > 0 && d.stack.includes("stripe") && !d.stack.includes("stripe-connect"))
358
+ s += 1;
359
+ return s;
360
+ },
361
+ reason: (d) => {
362
+ const bits = [];
363
+ if (d.stack.includes("stripe-connect"))
364
+ bits.push("Stripe Connect");
365
+ if (d.stack.includes("adyen-marketpay"))
366
+ bits.push("Adyen MarketPay");
367
+ if (d.stack.includes("persona") || d.stack.includes("onfido") || d.stack.includes("sumsub"))
368
+ bits.push("KYC vendor");
369
+ return `marketplace / two-sided platform (${bits.join(", ")}) — multi-party payouts + seller KYC + 1099-K + DSA`;
370
+ },
371
+ },
372
+ // ── enterprise-saas (B2B multi-tenant, SSO/SAML/SCIM, audit) ──
373
+ {
374
+ archetype: "enterprise-saas",
375
+ score: (d) => {
376
+ let s = 0;
377
+ if (d.stack.includes("workos"))
378
+ s += 8; // SSO/SCIM-as-a-service
379
+ if (d.stack.includes("auth0"))
380
+ s += 6;
381
+ if (d.stack.includes("okta"))
382
+ s += 6;
383
+ if (d.stack.includes("clerk"))
384
+ s += 4;
385
+ if (d.stack.includes("samlify"))
386
+ s += 6;
387
+ if (d.stack.includes("passport-saml"))
388
+ s += 6;
389
+ if (d.stack.includes("scim"))
390
+ s += 5;
391
+ if (d.readmeKeywords.includes("multi-tenant") || d.readmeKeywords.includes("multitenant"))
392
+ s += 4;
393
+ if (d.readmeKeywords.includes("enterprise") || d.readmeKeywords.includes("b2b"))
394
+ s += 3;
395
+ if (d.readmeKeywords.includes("sso") || d.readmeKeywords.includes("saml"))
396
+ s += 3;
397
+ // Stripe billing + multi-tenant signals = SaaS
398
+ if (s > 0 && d.stack.includes("stripe"))
399
+ s += 1;
400
+ return s;
401
+ },
402
+ reason: (d) => {
403
+ const bits = [];
404
+ if (d.stack.includes("workos"))
405
+ bits.push("WorkOS");
406
+ if (d.stack.includes("auth0"))
407
+ bits.push("Auth0");
408
+ if (d.stack.includes("okta"))
409
+ bits.push("Okta");
410
+ if (d.stack.includes("samlify") || d.stack.includes("passport-saml"))
411
+ bits.push("SAML lib");
412
+ if (d.stack.includes("scim"))
413
+ bits.push("SCIM");
414
+ return `enterprise B2B SaaS (${bits.join(", ")}) — multi-tenant isolation + SSO + audit log + SOC2 mandatory`;
415
+ },
416
+ },
417
+ // ── mlops (model training & lifecycle, distinct from inference) ─
418
+ {
419
+ archetype: "mlops",
420
+ score: (d) => {
421
+ let s = 0;
422
+ if (d.stack.includes("mlflow"))
423
+ s += 7;
424
+ if (d.stack.includes("wandb"))
425
+ s += 6;
426
+ if (d.stack.includes("dvc"))
427
+ s += 6;
428
+ if (d.stack.includes("kubeflow"))
429
+ s += 7;
430
+ if (d.stack.includes("bentoml"))
431
+ s += 5;
432
+ if (d.stack.includes("seldon"))
433
+ s += 5;
434
+ if (d.stack.includes("kserve"))
435
+ s += 5;
436
+ if (d.stack.includes("sagemaker"))
437
+ s += 5;
438
+ if (d.stack.includes("vertex-ai"))
439
+ s += 5;
440
+ if (d.stack.includes("ray"))
441
+ s += 4;
442
+ if (d.stack.includes("torch") || d.stack.includes("tensorflow"))
443
+ s += 2;
444
+ if (d.readmeKeywords.includes("training") && d.readmeKeywords.includes("model"))
445
+ s += 3;
446
+ // De-prioritize if pure inference (LLM API only)
447
+ if (s > 0 && d.stack.includes("anthropic-sdk") && !d.stack.includes("torch") && !d.stack.includes("mlflow"))
448
+ s -= 2;
449
+ return s;
450
+ },
451
+ reason: (d) => {
452
+ const bits = [];
453
+ if (d.stack.includes("mlflow"))
454
+ bits.push("MLflow");
455
+ if (d.stack.includes("wandb"))
456
+ bits.push("W&B");
457
+ if (d.stack.includes("dvc"))
458
+ bits.push("DVC");
459
+ if (d.stack.includes("kubeflow"))
460
+ bits.push("Kubeflow");
461
+ if (d.stack.includes("bentoml"))
462
+ bits.push("BentoML");
463
+ if (d.stack.includes("torch"))
464
+ bits.push("PyTorch");
465
+ if (d.stack.includes("tensorflow"))
466
+ bits.push("TensorFlow");
467
+ return `MLOps stack detected (${bits.join(", ")}) — dataset versioning + drift detection + model registry + EU AI Act high-risk gates`;
468
+ },
469
+ },
470
+ // ── streaming (event-driven / Kafka / Flink — distinct from batch data-platform) ─
471
+ {
472
+ archetype: "streaming",
473
+ score: (d) => {
474
+ let s = 0;
475
+ if (d.stack.includes("kafkajs"))
476
+ s += 7;
477
+ if (d.stack.includes("kafka-node"))
478
+ s += 6;
479
+ if (d.stack.includes("rdkafka"))
480
+ s += 7;
481
+ if (d.stack.includes("kinesis"))
482
+ s += 6;
483
+ if (d.stack.includes("pulsar"))
484
+ s += 6;
485
+ if (d.stack.includes("flink"))
486
+ s += 7;
487
+ if (d.stack.includes("beam"))
488
+ s += 6;
489
+ if (d.stack.includes("debezium"))
490
+ s += 7;
491
+ if (d.stack.includes("nats"))
492
+ s += 5;
493
+ if (d.stack.includes("rabbitmq"))
494
+ s += 4;
495
+ if (d.readmeKeywords.includes("streaming") || d.readmeKeywords.includes("event-driven"))
496
+ s += 2;
497
+ if (d.readmeKeywords.includes("cdc") || d.readmeKeywords.includes("real-time"))
498
+ s += 2;
499
+ return s;
500
+ },
501
+ reason: (d) => {
502
+ const bits = [];
503
+ if (d.stack.includes("kafkajs") || d.stack.includes("rdkafka") || d.stack.includes("kafka-node"))
504
+ bits.push("Kafka");
505
+ if (d.stack.includes("kinesis"))
506
+ bits.push("Kinesis");
507
+ if (d.stack.includes("pulsar"))
508
+ bits.push("Pulsar");
509
+ if (d.stack.includes("flink"))
510
+ bits.push("Flink");
511
+ if (d.stack.includes("beam"))
512
+ bits.push("Beam");
513
+ if (d.stack.includes("debezium"))
514
+ bits.push("Debezium CDC");
515
+ return `streaming / event-driven stack (${bits.join(", ")}) — exactly-once + backpressure + DLQ + schema evolution gates`;
516
+ },
517
+ },
518
+ // ── cms / content platform (headless CMS, publishing, SEO) ────
519
+ {
520
+ archetype: "cms",
521
+ score: (d) => {
522
+ let s = 0;
523
+ if (d.stack.includes("sanity"))
524
+ s += 9;
525
+ if (d.stack.includes("contentful"))
526
+ s += 9;
527
+ if (d.stack.includes("strapi"))
528
+ s += 9;
529
+ if (d.stack.includes("payload"))
530
+ s += 8;
531
+ if (d.stack.includes("ghost"))
532
+ s += 8;
533
+ if (d.stack.includes("gatsby"))
534
+ s += 5;
535
+ if (d.stack.includes("eleventy"))
536
+ s += 5;
537
+ if (d.stack.includes("hugo"))
538
+ s += 5;
539
+ if (d.stack.includes("astro") && d.readmeKeywords.includes("blog"))
540
+ s += 4;
541
+ if (d.stack.includes("next.js") && d.readmeKeywords.includes("blog"))
542
+ s += 3;
543
+ if (d.readmeKeywords.includes("cms") || d.readmeKeywords.includes("publishing") || d.readmeKeywords.includes("blog"))
544
+ s += 2;
545
+ // De-prioritize if it's clearly an enterprise SaaS app
546
+ if (s > 0 && (d.stack.includes("workos") || d.stack.includes("samlify")))
547
+ s -= 3;
548
+ return s;
549
+ },
550
+ reason: (d) => {
551
+ const bits = [];
552
+ if (d.stack.includes("sanity"))
553
+ bits.push("Sanity");
554
+ if (d.stack.includes("contentful"))
555
+ bits.push("Contentful");
556
+ if (d.stack.includes("strapi"))
557
+ bits.push("Strapi");
558
+ if (d.stack.includes("payload"))
559
+ bits.push("Payload");
560
+ if (d.stack.includes("ghost"))
561
+ bits.push("Ghost");
562
+ if (d.stack.includes("gatsby"))
563
+ bits.push("Gatsby");
564
+ if (d.stack.includes("eleventy"))
565
+ bits.push("Eleventy");
566
+ if (d.stack.includes("hugo"))
567
+ bits.push("Hugo");
568
+ return `CMS / publishing stack (${bits.join(", ")}) — schema.org + Core Web Vitals + DMCA + UGC moderation gates`;
569
+ },
570
+ },
571
+ // ── healthcare (FHIR/HL7/PHI) ───────────────────
572
+ {
573
+ archetype: "healthcare",
574
+ score: (d) => {
575
+ let s = 0;
576
+ if (d.stack.includes("fhir"))
577
+ s += 7;
578
+ if (d.stack.includes("hl7"))
579
+ s += 6;
580
+ if (d.readmeKeywords.includes("healthcare"))
581
+ s += 3;
582
+ return s;
583
+ },
584
+ reason: (d) => {
585
+ const bits = [];
586
+ if (d.stack.includes("fhir"))
587
+ bits.push("FHIR");
588
+ if (d.stack.includes("hl7"))
589
+ bits.push("HL7");
590
+ return `healthcare data tooling: ${bits.join(", ")} — HIPAA/PHI handling gates required`;
591
+ },
592
+ },
593
+ // ── regulated (compliance-first: DORA/NIS2/SOX/FedRAMP — not fintech/healthcare) ──
594
+ {
595
+ archetype: "regulated",
596
+ score: (d) => {
597
+ let s = 0;
598
+ // Compliance automation SaaS installed → near-certain regulated industry
599
+ if (d.stack.includes("compliance-automation"))
600
+ s += 10;
601
+ // Compliance documentation structure
602
+ if (d.stack.includes("compliance-docs"))
603
+ s += 7;
604
+ // Audit log package
605
+ if (d.stack.includes("audit-log"))
606
+ s += 5;
607
+ // README regulatory signals
608
+ const kws = d.readmeKeywords;
609
+ if (kws.includes("regulated"))
610
+ s += 5;
611
+ // FedRAMP / FISMA / CMMC → US federal regulated
612
+ const fedSignals = ["fedramp", "fisma", "cmmc"];
613
+ if (fedSignals.some((k) => kws.includes(k)))
614
+ s += 8;
615
+ // DORA ICT / NIS2 → EU regulated
616
+ if (kws.includes("dora ict") || kws.includes("nis2"))
617
+ s += 6;
618
+ // SOX compliance (non-fintech context)
619
+ if (kws.includes("sox compliance") || kws.includes("sarbanes")) {
620
+ if (!d.stack.includes("plaid") && !d.stack.includes("fintech"))
621
+ s += 5;
622
+ }
623
+ // Generic compliance/audit signals without stronger domain archetype
624
+ if (kws.includes("compliance automation") || kws.includes("audit trail"))
625
+ s += 4;
626
+ if (kws.includes("iso 27001") || kws.includes("soc 2 type"))
627
+ s += 3;
628
+ // Hard exclude: fintech and healthcare have their own dedicated archetypes
629
+ if (d.stack.includes("plaid") || d.stack.includes("wise") || d.stack.includes("fhir"))
630
+ s = 0;
631
+ return s;
632
+ },
633
+ reason: (d) => {
634
+ const bits = [];
635
+ if (d.stack.includes("compliance-automation"))
636
+ bits.push("compliance automation (Vanta/Drata/Secureframe)");
637
+ if (d.stack.includes("compliance-docs"))
638
+ bits.push("ISMS/risk-register/compliance docs");
639
+ if (d.stack.includes("audit-log"))
640
+ bits.push("audit log package");
641
+ const kws = d.readmeKeywords;
642
+ if (kws.includes("regulated"))
643
+ bits.push("regulated-industry README");
644
+ if (kws.includes("fedramp"))
645
+ bits.push("FedRAMP");
646
+ if (kws.includes("fisma"))
647
+ bits.push("FISMA");
648
+ if (kws.includes("cmmc"))
649
+ bits.push("CMMC");
650
+ if (kws.includes("dora ict"))
651
+ bits.push("DORA ICT");
652
+ if (kws.includes("nis2"))
653
+ bits.push("NIS2");
654
+ if (kws.includes("sox compliance") || kws.includes("sarbanes"))
655
+ bits.push("SOX");
656
+ return `regulated-industry signals (${bits.join(", ")}) — DORA/NIS2/SOX/FedRAMP compliance gates required`;
657
+ },
658
+ },
659
+ // ── cli-tool (explicit CLI: bin field + cli entry) ─
660
+ {
661
+ archetype: "cli-tool",
662
+ score: (d) => {
663
+ let s = 0;
664
+ // Explicit cli marker from detect.ts (bin field present)
665
+ if (d.stack.includes("cli"))
666
+ s += 6;
667
+ // Python CLI via [project.scripts] / console_scripts entry
668
+ if (d.stack.includes("python-cli"))
669
+ s += 7;
670
+ if (d.codeStructure.hasCliEntry)
671
+ s += 2;
672
+ if (d.readmeKeywords.includes("cli"))
673
+ s += 1;
674
+ // Penalize if web framework present (CLI + web is rare)
675
+ const webFw = ["next.js", "express", "fastify", "nestjs", "django", "fastapi", "flask", "hono", "koa"];
676
+ if (webFw.some((w) => d.stack.includes(w)))
677
+ s = Math.max(0, s - 4);
678
+ return s;
679
+ },
680
+ reason: (_d) => "package.json bin field + CLI entry detected — argument-parsing, exit-code, --help gates",
681
+ },
682
+ // ── mobile-app ───────────────────────────────────
683
+ {
684
+ archetype: "mobile-app",
685
+ score: (d) => {
686
+ let s = 0;
687
+ if (d.stack.includes("react-native"))
688
+ s += 5;
689
+ if (d.stack.includes("expo"))
690
+ s += 5;
691
+ if (d.stack.includes("ios"))
692
+ s += 5;
693
+ if (d.stack.includes("swift"))
694
+ s += 3;
695
+ return s;
696
+ },
697
+ reason: (d) => {
698
+ const bits = [];
699
+ if (d.stack.includes("react-native"))
700
+ bits.push("React Native");
701
+ if (d.stack.includes("expo"))
702
+ bits.push("Expo");
703
+ if (d.stack.includes("ios"))
704
+ bits.push("iOS project");
705
+ return `mobile framework detected: ${bits.join(", ")}`;
706
+ },
707
+ },
708
+ // ── data-platform ────────────────────────────────
709
+ {
710
+ archetype: "data-platform",
711
+ score: (d) => {
712
+ let s = 0;
713
+ if (d.stack.includes("data-pipeline"))
714
+ s += 6;
715
+ if (d.stack.includes("dbt"))
716
+ s += 3;
717
+ if (d.stack.includes("dagster"))
718
+ s += 3;
719
+ if (d.stack.includes("polars"))
720
+ s += 2;
721
+ if (d.stack.includes("iceberg"))
722
+ s += 3;
723
+ if (d.stack.includes("duckdb"))
724
+ s += 2;
725
+ if (d.readmeKeywords.includes("data"))
726
+ s += 1;
727
+ return s;
728
+ },
729
+ reason: (d) => {
730
+ const bits = [];
731
+ if (d.stack.includes("data-pipeline"))
732
+ bits.push("pandas/airflow/prefect");
733
+ if (d.stack.includes("dbt"))
734
+ bits.push("dbt");
735
+ if (d.stack.includes("dagster"))
736
+ bits.push("dagster");
737
+ return `data pipeline tooling: ${bits.join(", ") || "detected"}`;
738
+ },
739
+ },
740
+ // ── infra ────────────────────────────────────────
741
+ {
742
+ archetype: "infra",
743
+ score: (d) => {
744
+ const hasTerraform = d.stack.includes("terraform");
745
+ const hasHelm = d.stack.includes("helm");
746
+ const hasK8s = d.stack.includes("kubernetes");
747
+ // Require at least one explicit infra signal
748
+ if (!hasTerraform && !hasHelm && !hasK8s)
749
+ return 0;
750
+ let s = 0;
751
+ if (hasTerraform)
752
+ s += 4;
753
+ if (hasHelm)
754
+ s += 4;
755
+ if (hasK8s)
756
+ s += 4;
757
+ // Pure-infra repo (no app code) gets a small bonus
758
+ if (!d.stack.includes("nodejs") && !d.stack.includes("python") && !d.stack.includes("go"))
759
+ s += 2;
760
+ return s;
761
+ },
762
+ reason: (d) => {
763
+ const bits = [];
764
+ if (d.stack.includes("terraform"))
765
+ bits.push("Terraform");
766
+ if (d.stack.includes("helm"))
767
+ bits.push("Helm");
768
+ if (d.stack.includes("kubernetes"))
769
+ bits.push("Kustomize/K8s");
770
+ return `infrastructure-as-code detected: ${bits.join(", ")}`;
771
+ },
772
+ },
773
+ // ── web-service (default for web frameworks) ─────
774
+ {
775
+ archetype: "web-service",
776
+ score: (d) => {
777
+ let s = 0;
778
+ const serverFrameworks = ["express", "fastify", "nestjs", "hono", "koa",
779
+ "django", "fastapi", "flask", "gin", "echo", "chi"];
780
+ const fullstackFw = ["next.js", "nuxt", "remix", "sveltekit", "astro"];
781
+ const uiOnly = ["react", "vue", "angular", "svelte"];
782
+ for (const fw of serverFrameworks)
783
+ if (d.stack.includes(fw))
784
+ s += 3;
785
+ for (const fw of fullstackFw)
786
+ if (d.stack.includes(fw))
787
+ s += 4;
788
+ // UI-only: weaker signal (could be a library)
789
+ for (const fw of uiOnly)
790
+ if (d.stack.includes(fw))
791
+ s += 1;
792
+ if (s > 0)
793
+ s += 1; // baseline web bonus
794
+ // Code-structure boost
795
+ if (d.codeStructure.hasRoutesDir)
796
+ s += 2;
797
+ if (d.codeStructure.hasServerEntry)
798
+ s += 2;
799
+ if (d.scripts.hasStart || d.scripts.hasDev)
800
+ s += 1;
801
+ // Penalize if explicitly a library (publishConfig/cli)
802
+ if (d.stack.includes("library") && !d.codeStructure.hasRoutesDir && !d.codeStructure.hasServerEntry) {
803
+ s = Math.max(0, s - 3);
804
+ }
805
+ return s;
806
+ },
807
+ reason: (d) => {
808
+ const fw = d.stack.find((t) => ["next.js", "express", "fastify", "nestjs", "hono", "koa",
809
+ "django", "fastapi", "flask", "gin", "echo"].includes(t));
810
+ const extras = [];
811
+ if (d.codeStructure.hasRoutesDir)
812
+ extras.push("routes/");
813
+ if (d.codeStructure.hasServerEntry)
814
+ extras.push("server entry");
815
+ return `web framework detected: ${fw ?? "unknown"}${extras.length ? " + " + extras.join(", ") : ""}`;
816
+ },
817
+ },
818
+ // ── library (no app framework, just code) ────────
819
+ // Strong signal: detect.ts marked "library" + no web/server structure
820
+ {
821
+ archetype: "library",
822
+ score: (d) => {
823
+ const isExplicitLib = d.stack.includes("library");
824
+ const isExplicitCli = d.stack.includes("cli");
825
+ // Don't claim library if web-service shape is obvious
826
+ const looksLikeWebService = d.codeStructure.hasRoutesDir || d.codeStructure.hasServerEntry;
827
+ if (looksLikeWebService)
828
+ return 0;
829
+ // Don't claim library if data-pipeline / ml signals dominate
830
+ if (d.stack.includes("data-pipeline") || d.stack.includes("ml"))
831
+ return 0;
832
+ // Don't claim library if a domain-specific archetype is clearly present
833
+ const domainSignals = ["plaid", "wise", "dwolla", "fhir", "hl7", "stripe", "shopify", "solidity",
834
+ "embedded", "unity", "unreal", "godot", "react-native", "expo"];
835
+ if (domainSignals.some((s) => d.stack.includes(s)))
836
+ return 0;
837
+ if (isExplicitLib && !isExplicitCli)
838
+ return 7;
839
+ if (isExplicitLib && isExplicitCli)
840
+ return 4; // cli-tool rule will outscore
841
+ // Weaker signal: plain runtime + no app framework
842
+ const hasApp = d.stack.some((t) => ["next.js", "django", "fastapi", "flask", "express", "fastify", "nestjs", "hono",
843
+ "react-native", "expo", "tauri", "capacitor", "flutter",
844
+ "terraform", "pulumi", "aws-cdk", "helm",
845
+ "embedded", "zephyr", "esp-idf",
846
+ "browser-extension", "unity", "unreal", "godot", "phaser", "cocos"].includes(t));
847
+ if (hasApp)
848
+ return 0;
849
+ if (d.stack.includes("nodejs") || d.stack.includes("python") || d.stack.includes("go") || d.stack.includes("rust")) {
850
+ return 2;
851
+ }
852
+ return 0;
853
+ },
854
+ reason: (d) => {
855
+ if (d.stack.includes("library")) {
856
+ return "package.json/pyproject/Cargo.toml indicates a publishable library";
857
+ }
858
+ return "no web/mobile/infra framework detected — looks like a library/SDK";
859
+ },
860
+ },
861
+ // ── edtech (education technology — COPPA / FERPA / WCAG-AA child safety) ──
862
+ // Distinct from cms (general content) and healthcare (PHI). Drives age-gate,
863
+ // parental-consent, and accessibility patterns.
864
+ {
865
+ archetype: "edtech",
866
+ score: (d) => {
867
+ let s = 0;
868
+ const lmsLibs = ["canvas-lms", "moodle-api", "schoology-sdk", "blackboard-rest",
869
+ "google-classroom", "khan-academy-cli", "learnosity",
870
+ "kahoot-api", "h5p", "scorm", "lti", "lti-1.3"];
871
+ lmsLibs.forEach((l) => { if (d.stack.includes(l))
872
+ s += 6; });
873
+ // Auth/identity providers commonly used in edtech
874
+ if (d.stack.includes("clever-sdk"))
875
+ s += 6;
876
+ if (d.stack.includes("classlink-sso"))
877
+ s += 4;
878
+ const kws = d.readmeKeywords;
879
+ const eduKeywords = ["student", "classroom", "teacher", "k-12", "k12",
880
+ "lms", "learning management", "grade book", "gradebook",
881
+ "enrollment", "transcript", "pupil", "tutoring", "edtech"];
882
+ const matchedKws = eduKeywords.filter((k) => kws.includes(k));
883
+ if (matchedKws.length >= 2)
884
+ s += 5;
885
+ else if (matchedKws.length === 1)
886
+ s += 2;
887
+ // Strong signal: COPPA / FERPA explicitly mentioned
888
+ if (kws.includes("coppa") || kws.includes("ferpa"))
889
+ s += 6;
890
+ if (kws.includes("parental consent") || kws.includes("age gate"))
891
+ s += 4;
892
+ return s;
893
+ },
894
+ reason: (d) => {
895
+ const kws = d.readmeKeywords;
896
+ const bits = [];
897
+ const lmsLibs = ["canvas-lms", "moodle-api", "schoology-sdk", "google-classroom", "lti", "scorm"];
898
+ lmsLibs.forEach((l) => { if (d.stack.includes(l))
899
+ bits.push(l); });
900
+ if (kws.includes("coppa"))
901
+ bits.push("COPPA mention");
902
+ if (kws.includes("ferpa"))
903
+ bits.push("FERPA mention");
904
+ if (kws.includes("k-12") || kws.includes("k12"))
905
+ bits.push("K-12 keyword");
906
+ if (kws.includes("student"))
907
+ bits.push("student-data keyword");
908
+ return `edtech detected (${bits.join(", ") || "education domain signals"}) — COPPA/FERPA/WCAG-AA child-safety gates required`;
909
+ },
910
+ },
911
+ // ── gov-public (government / civic-tech — FedRAMP / NIST 800-53 / Section 508) ──
912
+ // Severe regulatory burden. Distinct from regulated (which is more EU-focused
913
+ // DORA/NIS2). gov-public targets US federal/state + UK gov.uk patterns.
914
+ {
915
+ archetype: "gov-public",
916
+ score: (d) => {
917
+ let s = 0;
918
+ const govLibs = ["login-gov-sdk", "id-me-sdk", "idme-sdk",
919
+ "usds-design-system", "uswds", "uk-gov-design-system",
920
+ "gov-uk-frontend", "verify-gov-uk",
921
+ "usajobs-sdk", "data-gov", "irs-modernized-efile"];
922
+ govLibs.forEach((l) => { if (d.stack.includes(l))
923
+ s += 6; });
924
+ const kws = d.readmeKeywords;
925
+ const govKeywords = ["fedramp", "fisma", "nist 800-53", "nist-800-53",
926
+ "section 508", "section-508", "ato", "civic tech",
927
+ "government", "municipal", "federal", "agency",
928
+ "gov.uk", "usds", "data.gov", "usa.gov", "irs", "ssa",
929
+ "department of", "stateramp", "cjis"];
930
+ const matchedKws = govKeywords.filter((k) => kws.includes(k));
931
+ if (matchedKws.length >= 2)
932
+ s += 6;
933
+ else if (matchedKws.length === 1)
934
+ s += 3;
935
+ // Very strong signals
936
+ if (kws.includes("fedramp") || kws.includes("fisma"))
937
+ s += 4;
938
+ if (kws.includes("section 508") || kws.includes("section-508"))
939
+ s += 3;
940
+ if (kws.includes("ato"))
941
+ s += 3;
942
+ return s;
943
+ },
944
+ reason: (d) => {
945
+ const kws = d.readmeKeywords;
946
+ const bits = [];
947
+ if (d.stack.includes("login-gov-sdk"))
948
+ bits.push("login.gov");
949
+ if (d.stack.includes("usds-design-system") || d.stack.includes("uswds"))
950
+ bits.push("USWDS");
951
+ if (d.stack.includes("uk-gov-design-system") || d.stack.includes("gov-uk-frontend"))
952
+ bits.push("gov.uk Design System");
953
+ if (kws.includes("fedramp"))
954
+ bits.push("FedRAMP mention");
955
+ if (kws.includes("nist-800-53") || kws.includes("nist 800-53"))
956
+ bits.push("NIST 800-53 mention");
957
+ if (kws.includes("section 508") || kws.includes("section-508"))
958
+ bits.push("Section 508 mention");
959
+ return `gov-public detected (${bits.join(", ") || "government domain signals"}) — FedRAMP/NIST 800-53/Section 508 gates required`;
960
+ },
961
+ },
962
+ // ── defense-govcon (US defense contractor — CMMC / NIST 800-171 / DFARS) ──
963
+ // Distinct from gov-public: the buyer is DoD and the obligation is protecting
964
+ // Controlled Unclassified Information (CUI) under DFARS 252.204-7012 + CMMC 2.0,
965
+ // with export controls (ITAR/EAR) and supply-chain bans (Section 889).
966
+ {
967
+ archetype: "defense-govcon",
968
+ score: (d) => {
969
+ let s = 0;
970
+ const kws = d.readmeKeywords;
971
+ // detect.ts "defense" bucket fires on cmmc / nist 800-171 / dfars / cui / itar / section 889.
972
+ // These are unambiguous DoD-contractor signals — more specific than generic
973
+ // gov-public (fedramp/government), so weight high enough to outrank it.
974
+ if (kws.includes("defense"))
975
+ s += 12;
976
+ return s;
977
+ },
978
+ reason: (_d) => "US defense-contractor signals (CMMC / NIST 800-171 / DFARS 252.204-7012 / CUI / ITAR / Section 889) — CMMC assessment + CUI protection gates required",
979
+ },
980
+ // ── insurance (insurtech — NAIC / Solvency II / actuarial / claims fraud) ──
981
+ // Fintech-adjacent but distinct: multi-state filings, anti-discrimination
982
+ // pricing, actuarial model auditability, claims fraud detection.
983
+ {
984
+ archetype: "insurance",
985
+ score: (d) => {
986
+ let s = 0;
987
+ const insuranceLibs = ["acord-standards", "naic-schemas", "drools-rules",
988
+ "solvency2-calc", "openexposure", "ms-actuarial",
989
+ "lloyds-vendor-api", "verisk-sdk", "ccc-one-sdk",
990
+ "guidewire-cloud", "duck-creek", "majesco-sdk",
991
+ "ebix", "aplus-pas"];
992
+ insuranceLibs.forEach((l) => { if (d.stack.includes(l))
993
+ s += 6; });
994
+ const kws = d.readmeKeywords;
995
+ const insuranceKeywords = ["policy", "underwriting", "premium", "claim",
996
+ "actuarial", "reinsurance", "naic", "solvency",
997
+ "broker", "carrier", "mga", "mgu", "tpa",
998
+ "insurance", "insurtech", "insurer", "insured",
999
+ "deductible", "coverage", "bordereau"];
1000
+ const matchedKws = insuranceKeywords.filter((k) => kws.includes(k));
1001
+ if (matchedKws.length >= 3)
1002
+ s += 6;
1003
+ else if (matchedKws.length === 2)
1004
+ s += 3;
1005
+ else if (matchedKws.length === 1)
1006
+ s += 1;
1007
+ // Very strong signals — NAIC/Solvency/IFRS 17 explicit
1008
+ if (kws.includes("naic") || kws.includes("solvency ii") || kws.includes("solvency-ii"))
1009
+ s += 5;
1010
+ if (kws.includes("ifrs 17") || kws.includes("ifrs-17"))
1011
+ s += 4;
1012
+ if (kws.includes("insurtech"))
1013
+ s += 4;
1014
+ // Don't double-score: if also matches commerce/fintech, subtract
1015
+ // since insurance is distinct domain (not generic fintech)
1016
+ if (d.stack.includes("stripe") && !insuranceLibs.some((l) => d.stack.includes(l))) {
1017
+ s = Math.max(0, s - 2);
1018
+ }
1019
+ return s;
1020
+ },
1021
+ reason: (d) => {
1022
+ const kws = d.readmeKeywords;
1023
+ const bits = [];
1024
+ const insuranceLibs = ["acord-standards", "naic-schemas", "guidewire-cloud", "duck-creek", "majesco-sdk"];
1025
+ insuranceLibs.forEach((l) => { if (d.stack.includes(l))
1026
+ bits.push(l); });
1027
+ if (kws.includes("naic"))
1028
+ bits.push("NAIC mention");
1029
+ if (kws.includes("solvency ii") || kws.includes("solvency-ii"))
1030
+ bits.push("Solvency II mention");
1031
+ if (kws.includes("actuarial"))
1032
+ bits.push("actuarial keyword");
1033
+ if (kws.includes("underwriting"))
1034
+ bits.push("underwriting keyword");
1035
+ if (kws.includes("insurtech"))
1036
+ bits.push("insurtech keyword");
1037
+ return `insurance detected (${bits.join(", ") || "insurance domain signals"}) — NAIC/Solvency II/actuarial-audit gates required`;
1038
+ },
1039
+ },
1040
+ ];
1041
+ // Tie-break priority — when two rules score equally, prefer the one
1042
+ // higher in this list (more specific / domain-bound first).
1043
+ const TIE_BREAK_PRIORITY = [
1044
+ "browser-extension", "iot-embedded", "web3", "game",
1045
+ "agent-product", "fintech", "insurance", "healthcare", "edtech", "defense-govcon", "gov-public", "marketplace",
1046
+ "mlops", "streaming",
1047
+ "commerce", "enterprise-saas", "ai-system", "devtools",
1048
+ "data-platform", "cms", "infra", "mobile-app",
1049
+ "regulated", "cli-tool", "web-service", "library", "greenfield",
1050
+ ];
1051
+ function priorityIndex(a) {
1052
+ const i = TIE_BREAK_PRIORITY.indexOf(a);
1053
+ return i < 0 ? TIE_BREAK_PRIORITY.length : i;
1054
+ }
1055
+ export function pickArchetype(d) {
1056
+ const scored = RULES
1057
+ .map((r) => ({ archetype: r.archetype, score: r.score(d), reason: r.reason(d) }))
1058
+ .filter((r) => r.score > 0)
1059
+ .sort((a, b) => {
1060
+ if (b.score !== a.score)
1061
+ return b.score - a.score;
1062
+ return priorityIndex(a.archetype) - priorityIndex(b.archetype);
1063
+ });
1064
+ if (scored.length === 0) {
1065
+ return {
1066
+ primary: "greenfield",
1067
+ confidence: "low",
1068
+ rationale: "no strong signals detected — treating as greenfield project",
1069
+ alternatives: [],
1070
+ };
1071
+ }
1072
+ const top = scored[0];
1073
+ const nextBest = scored[1]?.score ?? 0;
1074
+ const gap = top.score - nextBest;
1075
+ // Confidence: high when score≥6 and gap≥3; medium ≥4; else low
1076
+ const confidence = top.score >= 6 && gap >= 3 ? "high" :
1077
+ top.score >= 4 && gap >= 1 ? "medium" : "low";
1078
+ // Dedupe alternatives, keep top 3
1079
+ const seen = new Set([top.archetype]);
1080
+ const alternatives = [];
1081
+ for (const r of scored.slice(1)) {
1082
+ if (!seen.has(r.archetype)) {
1083
+ seen.add(r.archetype);
1084
+ alternatives.push(r.archetype);
1085
+ if (alternatives.length >= 3)
1086
+ break;
1087
+ }
1088
+ }
1089
+ // ── Pack hints for low/medium confidence or niche stacks ────────────────
1090
+ // Surfaces domain-specific packs that the archetype alone doesn't capture.
1091
+ const suggestedPacks = confidence !== "high"
1092
+ ? inferPackHints(d)
1093
+ : inferPackHints(d).filter((p) => isNichePack(p)); // always surface niche packs
1094
+ return {
1095
+ primary: top.archetype,
1096
+ confidence,
1097
+ rationale: top.reason,
1098
+ alternatives,
1099
+ ...(suggestedPacks.length > 0 ? { suggestedPacks } : {}),
1100
+ };
1101
+ }
1102
+ /** Niche packs that should surface even at high confidence */
1103
+ function isNichePack(_pack) {
1104
+ return false;
1105
+ }
1106
+ /**
1107
+ * Infer likely domain packs from README/infra keywords when the archetype
1108
+ * scorer doesn't have a dedicated high-confidence rule for the domain.
1109
+ */
1110
+ function inferPackHints(d) {
1111
+ const hints = [];
1112
+ const kws = new Set([...d.readmeKeywords, ...(d.infraKeywords ?? [])]);
1113
+ const has = (...terms) => terms.some((t) => kws.has(t));
1114
+ if (has("recruit", "hiring", "candidate", "ats", "aedt"))
1115
+ hints.push("hr-ai-pack");
1116
+ if (has("voice", "telephony", "ivr", "stt", "tts", "outbound call"))
1117
+ hints.push("voice-pack");
1118
+ if (has("public api", "api key", "developer portal", "openapi"))
1119
+ hints.push("api-platform-pack");
1120
+ return hints;
1121
+ }
1122
+ // Compliance hints — auto-suggested based on stack and README.
1123
+ export function suggestCompliance(d, archetype) {
1124
+ const c = new Set();
1125
+ // ── archetype defaults ───────────────────────────
1126
+ if (archetype === "commerce") {
1127
+ c.add("pci-dss");
1128
+ c.add("gdpr");
1129
+ }
1130
+ if (archetype === "fintech") {
1131
+ c.add("pci-dss");
1132
+ c.add("sox");
1133
+ c.add("kyc-aml");
1134
+ c.add("gdpr");
1135
+ }
1136
+ if (archetype === "healthcare") {
1137
+ c.add("hipaa");
1138
+ c.add("gdpr");
1139
+ c.add("hitech");
1140
+ }
1141
+ if (archetype === "ai-system" || archetype === "agent-product") {
1142
+ c.add("eu-ai-act");
1143
+ // Add OWASP LLM only if it's truly an agent product
1144
+ if (archetype === "agent-product")
1145
+ c.add("owasp-llm-top-10");
1146
+ }
1147
+ if (archetype === "web3") {
1148
+ c.add("soc2");
1149
+ }
1150
+ if (archetype === "iot-embedded") {
1151
+ c.add("iso27001");
1152
+ c.add("etsi-en-303-645");
1153
+ }
1154
+ if (archetype === "browser-extension") {
1155
+ c.add("csp");
1156
+ c.add("mv3-security");
1157
+ c.add("gdpr");
1158
+ }
1159
+ if (archetype === "game") {
1160
+ c.add("coppa");
1161
+ c.add("age-rating");
1162
+ c.add("accessibility");
1163
+ }
1164
+ if (archetype === "devtools") {
1165
+ c.add("openssf");
1166
+ c.add("api-stability");
1167
+ c.add("soc2-type-2");
1168
+ c.add("gdpr");
1169
+ }
1170
+ if (archetype === "regulated") {
1171
+ c.add("iso27001");
1172
+ c.add("gdpr");
1173
+ c.add("compliance-required");
1174
+ // FedRAMP/CMMC if US federal signals present
1175
+ const kws = d.readmeKeywords;
1176
+ if (kws.includes("fedramp") || kws.includes("fisma") || kws.includes("cmmc"))
1177
+ c.add("fedramp");
1178
+ if (kws.includes("dora ict") || kws.includes("nis2")) {
1179
+ c.add("dora-ict");
1180
+ c.add("nis2");
1181
+ }
1182
+ if (kws.includes("sox compliance") || kws.includes("sarbanes"))
1183
+ c.add("sox");
1184
+ }
1185
+ if (archetype === "web-service")
1186
+ c.add("gdpr"); // baseline for user data
1187
+ if (archetype === "cli-tool") { /* CLI tools usually don't have compliance load */ }
1188
+ if (archetype === "marketplace") {
1189
+ c.add("pci-dss");
1190
+ c.add("kyc-aml");
1191
+ c.add("gdpr");
1192
+ c.add("dsa-eu");
1193
+ c.add("p2b-eu");
1194
+ c.add("1099-k");
1195
+ }
1196
+ if (archetype === "enterprise-saas") {
1197
+ c.add("soc2-type-2");
1198
+ c.add("iso27001");
1199
+ c.add("gdpr");
1200
+ c.add("ccpa");
1201
+ }
1202
+ if (archetype === "mlops") {
1203
+ c.add("eu-ai-act");
1204
+ c.add("nist-ai-rmf");
1205
+ c.add("iso42001");
1206
+ }
1207
+ if (archetype === "streaming") {
1208
+ c.add("gdpr"); // event retention rules
1209
+ }
1210
+ if (archetype === "cms") {
1211
+ c.add("dmca");
1212
+ c.add("wcag-2.2");
1213
+ c.add("gdpr");
1214
+ c.add("dsa-eu");
1215
+ }
1216
+ if (archetype === "edtech") {
1217
+ c.add("coppa");
1218
+ c.add("ferpa");
1219
+ c.add("gdpr-k");
1220
+ c.add("wcag-2.2-aa");
1221
+ c.add("section-508");
1222
+ // State student-privacy laws
1223
+ c.add("sopipa-ca");
1224
+ }
1225
+ if (archetype === "gov-public") {
1226
+ c.add("fedramp");
1227
+ c.add("nist-800-53");
1228
+ c.add("fisma");
1229
+ c.add("section-508");
1230
+ c.add("pia");
1231
+ // CJIS only if law-enforcement keywords present
1232
+ const kws = d.readmeKeywords;
1233
+ if (kws.includes("cjis") || kws.includes("law enforcement") || kws.includes("criminal justice")) {
1234
+ c.add("cjis");
1235
+ }
1236
+ // StateRAMP if state-level
1237
+ if (kws.includes("stateramp") || kws.includes("state government"))
1238
+ c.add("stateramp");
1239
+ c.add("ato"); // Authority to Operate
1240
+ }
1241
+ if (archetype === "insurance") {
1242
+ c.add("naic");
1243
+ c.add("solvency-ii");
1244
+ c.add("ifrs-17");
1245
+ c.add("gdpr");
1246
+ c.add("ccpa");
1247
+ c.add("anti-discrimination-pricing");
1248
+ c.add("actuarial-asops");
1249
+ c.add("state-doi"); // Department of Insurance per US state
1250
+ c.add("naic-ai-model-bulletin");
1251
+ c.add("colorado-sb-21-169"); // US insurance-AI rules
1252
+ }
1253
+ if (archetype === "defense-govcon") {
1254
+ c.add("cmmc-2.0");
1255
+ c.add("nist-800-171");
1256
+ c.add("dfars-252.204-7012");
1257
+ c.add("itar");
1258
+ c.add("ear");
1259
+ c.add("section-889");
1260
+ c.add("fedramp");
1261
+ }
1262
+ // ── stack-derived (cross-archetype) ──────────────
1263
+ if (d.stack.includes("stripe") || d.stack.includes("braintree") ||
1264
+ d.stack.includes("adyen") || d.stack.includes("paddle")) {
1265
+ c.add("pci-dss");
1266
+ }
1267
+ if (d.stack.includes("plaid") || d.stack.includes("dwolla") ||
1268
+ d.stack.includes("teller") || d.stack.includes("wise")) {
1269
+ c.add("kyc-aml");
1270
+ c.add("sox");
1271
+ }
1272
+ if (d.stack.includes("fhir") || d.stack.includes("hl7")) {
1273
+ c.add("hipaa");
1274
+ c.add("hitech");
1275
+ }
1276
+ // ── README-derived ───────────────────────────────
1277
+ if (d.readmeKeywords.includes("regulated"))
1278
+ c.add("compliance-required");
1279
+ if (d.readmeKeywords.includes("healthcare"))
1280
+ c.add("hipaa");
1281
+ if (d.readmeKeywords.includes("fintech"))
1282
+ c.add("sox");
1283
+ return Array.from(c).sort();
1284
+ }
1285
+ /**
1286
+ * Which review agents fire for each archetype. The reviewer name maps to
1287
+ * `agents/<name>.md` in the great_cto plugin. Naming aliases exist for
1288
+ * historical reasons (pci-reviewer covers fintech, firmware-reviewer
1289
+ * covers iot-embedded, etc.) — documented in
1290
+ * docs/agents/REVIEWER-NAMING.md (gap A2 closure).
1291
+ *
1292
+ * `greenfield` has no reviewers — pipeline runs in nano mode only.
1293
+ */
1294
+ export const REVIEWERS_BY_ARCHETYPE = {
1295
+ "web-service": ["security-officer"],
1296
+ "mobile-app": ["mobile-store-reviewer", "security-officer"],
1297
+ "ai-system": ["ai-security-reviewer", "ai-prompt-architect", "ai-eval-engineer"],
1298
+ "agent-product": ["ai-security-reviewer", "ai-prompt-architect", "ai-eval-engineer"],
1299
+ "mlops": ["mlops-reviewer", "ai-security-reviewer"],
1300
+ "data-platform": ["data-platform-reviewer"],
1301
+ "streaming": ["streaming-reviewer"],
1302
+ "infra": ["infra-reviewer"],
1303
+ "library": ["library-reviewer"],
1304
+ "cli-tool": ["cli-reviewer"],
1305
+ "commerce": ["pci-reviewer", "security-officer"],
1306
+ "marketplace": ["marketplace-reviewer", "pci-reviewer"],
1307
+ "fintech": ["pci-reviewer", "regulated-reviewer"],
1308
+ "healthcare": ["healthcare-reviewer", "security-officer"],
1309
+ "web3": ["oracle-reviewer"],
1310
+ "iot-embedded": ["firmware-reviewer"],
1311
+ "regulated": ["regulated-reviewer"],
1312
+ "devtools": ["devtools-reviewer"],
1313
+ "browser-extension": ["web-store-reviewer"],
1314
+ "game": ["game-reviewer"],
1315
+ "cms": ["cms-reviewer"],
1316
+ "enterprise-saas": ["enterprise-saas-reviewer"],
1317
+ "edtech": ["edtech-reviewer"],
1318
+ "gov-public": ["gov-reviewer", "security-officer"],
1319
+ "insurance": ["insurance-reviewer", "regulated-reviewer"],
1320
+ "defense-govcon": ["cmmc-reviewer", "gov-reviewer", "security-officer"],
1321
+ // Product Builder archetypes (A1–A6) — lean reviewer sets; payments → pci.
1322
+ "vertical-saas": ["security-officer"],
1323
+ "booking": ["pci-reviewer", "security-officer"],
1324
+ "crm": ["security-officer"],
1325
+ "dashboard": ["security-officer"],
1326
+ "content-platform": ["pci-reviewer", "security-officer"],
1327
+ "marketplace-lite": ["pci-reviewer", "security-officer"],
1328
+ "greenfield": [],
1329
+ };
1330
+ /**
1331
+ * Which human gates the pipeline opens for each archetype at the medium
1332
+ * project_size. Smaller sizes skip gates (see `gatesFor()` below); larger
1333
+ * sizes may add compliance.
1334
+ *
1335
+ * NOTE: nano → only [plan]. Enterprise → always adds compliance.
1336
+ */
1337
+ export const GATES_BY_ARCHETYPE = {
1338
+ "web-service": ["plan", "qa", "ship"],
1339
+ "mobile-app": ["plan", "qa", "ship"],
1340
+ "ai-system": ["plan", "cost", "qa", "security", "ship"],
1341
+ "agent-product": ["plan", "cost", "qa", "security", "ship"],
1342
+ "mlops": ["plan", "cost", "qa", "security", "ship"],
1343
+ "data-platform": ["plan", "qa", "ship"],
1344
+ "streaming": ["plan", "qa", "ship"],
1345
+ "infra": ["plan", "qa", "ship"],
1346
+ "library": ["plan", "qa", "ship"],
1347
+ "cli-tool": ["plan", "qa", "ship"],
1348
+ "commerce": ["plan", "qa", "security", "ship", "compliance"],
1349
+ "marketplace": ["plan", "qa", "security", "ship", "compliance"],
1350
+ "fintech": ["plan", "qa", "security", "ship", "compliance"],
1351
+ "healthcare": ["plan", "qa", "security", "ship", "compliance"],
1352
+ "web3": ["plan", "qa", "oracle-review", "security", "ship"],
1353
+ "iot-embedded": ["plan", "qa", "security", "ship"],
1354
+ "regulated": ["plan", "qa", "security", "ship", "compliance"],
1355
+ "devtools": ["plan", "qa", "ship"],
1356
+ "browser-extension": ["plan", "qa", "ship"],
1357
+ "game": ["plan", "qa", "ship"],
1358
+ "cms": ["plan", "qa", "ship"],
1359
+ "enterprise-saas": ["plan", "qa", "security", "ship", "compliance"],
1360
+ "edtech": ["plan", "qa", "edtech-review", "security", "ship", "compliance"],
1361
+ "gov-public": ["plan", "qa", "gov-review", "security", "ship", "compliance"],
1362
+ "insurance": ["plan", "qa", "insurance-review", "security", "ship", "compliance"],
1363
+ "defense-govcon": ["plan", "qa", "cmmc-assessment", "security", "ship", "compliance"],
1364
+ // Product Builder archetypes (A1–A6) — non-regulated baseline; the single CTO gate
1365
+ // emerges at change_tier T1 (effectiveGates → [plan]).
1366
+ "vertical-saas": ["plan", "qa", "ship"],
1367
+ "booking": ["plan", "qa", "ship"],
1368
+ "crm": ["plan", "qa", "ship"],
1369
+ "dashboard": ["plan", "qa", "ship"],
1370
+ "content-platform": ["plan", "qa", "ship"],
1371
+ "marketplace-lite": ["plan", "qa", "ship"],
1372
+ "greenfield": ["plan"],
1373
+ };
1374
+ /**
1375
+ * Returns the subset of gates the pipeline will actually open for a
1376
+ * given archetype + project_size. Used by the orchestrator before
1377
+ * opening any gate to skip unnecessary human checkpoints on smaller
1378
+ * projects (e.g. nano always skips QA).
1379
+ */
1380
+ export function gatesFor(archetype, size) {
1381
+ const all = GATES_BY_ARCHETYPE[archetype] ?? [];
1382
+ if (size === "nano")
1383
+ return all.filter((g) => g === "plan");
1384
+ if (size === "small")
1385
+ return all.filter((g) => g === "plan" || g === "ship");
1386
+ if (size === "medium")
1387
+ return all;
1388
+ // large + enterprise → ensure compliance is included
1389
+ return all.includes("compliance") ? all : [...all, "compliance"];
1390
+ }
1391
+ /** Gates that form the non-negotiable floor for a *regulated* archetype. */
1392
+ const FLOOR_GATES = ["security", "compliance", "ship"];
1393
+ /**
1394
+ * The human gates the pipeline opens for a change, given its archetype, the
1395
+ * project size, AND the per-change risk tier. Composes with `gatesFor()`:
1396
+ *
1397
+ * - T2 (default): the full size baseline, with `ship` guaranteed even if a
1398
+ * small project_size had stripped it. An irreversible change always gets a
1399
+ * final go/no-go. Nothing is ever removed → fail-safe for an unknown tier.
1400
+ * - T1: only `plan` (you review intent) PLUS the regulated floor. The standalone
1401
+ * qa/security-as-ceremony is dropped because CI + tests cover a reversible change.
1402
+ * - T0: the regulated floor only — for a plain archetype that is the empty set
1403
+ * (CI is the gate); for a regulated one, security+compliance+ship still fire.
1404
+ *
1405
+ * An archetype is "regulated" iff its baseline contains `security` or `compliance`.
1406
+ * That is what keeps the downgrade sound: a fintech repo cannot ship an
1407
+ * irreversible change unreviewed even when the change is labelled a fix.
1408
+ *
1409
+ * NOTE: change-specific T2 triggers (a migration, a new write-connector, a prod
1410
+ * deploy) are resolved upstream by the classifier, which escalates such a change
1411
+ * to T2 before this function sees it. Here we only map an already-decided tier.
1412
+ */
1413
+ export function effectiveGates(archetype, size, tier = "T2") {
1414
+ const base = gatesFor(archetype, size);
1415
+ // T2 (and any unrecognized tier) → full baseline, never downgraded, ship forced.
1416
+ if (tier !== "T0" && tier !== "T1") {
1417
+ return base.includes("ship") ? base : [...base, "ship"];
1418
+ }
1419
+ // Regulated archetypes carry a floor that survives every downgrade.
1420
+ const isRegulated = base.includes("security") || base.includes("compliance");
1421
+ const floor = isRegulated ? base.filter((g) => FLOOR_GATES.includes(g)) : [];
1422
+ if (tier === "T1") {
1423
+ const keep = new Set(floor);
1424
+ if (base.includes("plan"))
1425
+ keep.add("plan");
1426
+ return base.filter((g) => keep.has(g)); // preserve base order
1427
+ }
1428
+ // T0 — maintenance.
1429
+ return floor;
1430
+ }
1431
+ /**
1432
+ * Returns the ordered list of reviewers for an archetype. Empty for
1433
+ * `greenfield`. Used by the orchestrator to spawn the right
1434
+ * archetype-specific review stages after senior-dev.
1435
+ */
1436
+ export function reviewersFor(archetype) {
1437
+ return REVIEWERS_BY_ARCHETYPE[archetype] ?? [];
1438
+ }
1439
+ /**
1440
+ * Horizontal builder agents (the "obвязка") wired into the build pipeline per
1441
+ * Product-Builder archetype: integrations + migration + billing are universal (every
1442
+ * SMB product integrates, imports from an incumbent, and is a paid product —
1443
+ * subscription-billing-engineer self-scopes to one-off when there's no subscription).
1444
+ * connector-builder adds to dashboards (ingest), media-pipeline-engineer to content
1445
+ * platforms. geo-routing-engineer and mobile-app-builder are signal-gated in
1446
+ * compileFlow (routing/maps signals; mobile/field signals), not archetype-static.
1447
+ */
1448
+ const SMB_BUILD_LAYER = ["app-scaffolder", "auth-engineer"];
1449
+ const SMB_OBVYAZKA = ["integrations-engineer", "migration-import-engineer", "subscription-billing-engineer"];
1450
+ export const BUILDERS_BY_ARCHETYPE = {
1451
+ "vertical-saas": [...SMB_BUILD_LAYER, ...SMB_OBVYAZKA],
1452
+ "booking": [...SMB_BUILD_LAYER, ...SMB_OBVYAZKA],
1453
+ "crm": [...SMB_BUILD_LAYER, ...SMB_OBVYAZKA],
1454
+ "dashboard": [...SMB_BUILD_LAYER, ...SMB_OBVYAZKA, "connector-builder"],
1455
+ "content-platform": [...SMB_BUILD_LAYER, ...SMB_OBVYAZKA, "media-pipeline-engineer"],
1456
+ "marketplace-lite": [...SMB_BUILD_LAYER, ...SMB_OBVYAZKA],
1457
+ };
1458
+ /** Builder agents for an archetype (the obвязка layer). Empty for non-SMB archetypes. */
1459
+ export function buildersFor(archetype) {
1460
+ return BUILDERS_BY_ARCHETYPE[archetype] ?? [];
1461
+ }