great-cto 1.0.169 → 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.
@@ -51,9 +51,9 @@ const RULES = [
51
51
  if (d.stack.includes("devtools-api"))
52
52
  s += 7;
53
53
  if (d.stack.includes("openapi-spec"))
54
- s += 4;
54
+ s += 8;
55
55
  if (d.stack.includes("graphql-schema"))
56
- s += 3;
56
+ s += 6;
57
57
  if (d.stack.includes("multi-sdk"))
58
58
  s += 4;
59
59
  if (d.stack.includes("stainless"))
@@ -183,25 +183,30 @@ const RULES = [
183
183
  score: (d) => {
184
184
  let s = 0;
185
185
  if (d.stack.includes("anthropic-sdk"))
186
- s += 4;
186
+ s += 6;
187
187
  if (d.stack.includes("openai-sdk"))
188
- s += 3;
188
+ s += 5;
189
189
  if (d.stack.includes("google-ai"))
190
- s += 3;
190
+ s += 5;
191
191
  if (d.stack.includes("aws-bedrock"))
192
- s += 3;
192
+ s += 5;
193
193
  if (d.stack.includes("cohere"))
194
- s += 2;
194
+ s += 4;
195
195
  if (d.stack.includes("replicate"))
196
- s += 2;
196
+ s += 4;
197
197
  if (d.stack.includes("langchain"))
198
- s += 3;
198
+ s += 5;
199
199
  if (d.stack.includes("llamaindex"))
200
- s += 3;
200
+ s += 5;
201
201
  if (d.stack.includes("vercel-ai-sdk"))
202
- s += 3;
202
+ s += 5;
203
203
  if (d.stack.includes("ml"))
204
204
  s += 2;
205
+ // Combo bonus: using multiple AI providers/frameworks together
206
+ const aiCount = ["anthropic-sdk", "openai-sdk", "google-ai", "aws-bedrock", "cohere", "replicate", "langchain", "llamaindex", "vercel-ai-sdk"]
207
+ .filter(x => d.stack.includes(x)).length;
208
+ if (aiCount >= 2)
209
+ s += 3;
205
210
  // Don't double-score if already an agent-product
206
211
  const agents = ["langgraph", "crewai", "autogen", "mastra", "mcp"];
207
212
  if (agents.some((a) => d.stack.includes(a)))
@@ -264,6 +269,241 @@ const RULES = [
264
269
  return `fintech integration: ${bits.join(", ")} — SOX, PCI, KYC/AML compliance gates`;
265
270
  },
266
271
  },
272
+ // ── marketplace (two-sided platform — Stripe Connect / KYC) ────
273
+ // Stronger than commerce when payouts to sellers + KYC providers detected
274
+ {
275
+ archetype: "marketplace",
276
+ score: (d) => {
277
+ let s = 0;
278
+ if (d.stack.includes("stripe-connect"))
279
+ s += 9;
280
+ if (d.stack.includes("adyen-marketpay"))
281
+ s += 9;
282
+ if (d.stack.includes("persona"))
283
+ s += 5;
284
+ if (d.stack.includes("onfido"))
285
+ s += 5;
286
+ if (d.stack.includes("sumsub"))
287
+ s += 5;
288
+ if (d.readmeKeywords.includes("marketplace") || d.readmeKeywords.includes("two-sided"))
289
+ s += 4;
290
+ if (d.readmeKeywords.includes("seller") && d.readmeKeywords.includes("buyer"))
291
+ s += 3;
292
+ // Don't score if pure single-merchant commerce
293
+ if (s > 0 && d.stack.includes("stripe") && !d.stack.includes("stripe-connect"))
294
+ s += 1;
295
+ return s;
296
+ },
297
+ reason: (d) => {
298
+ const bits = [];
299
+ if (d.stack.includes("stripe-connect"))
300
+ bits.push("Stripe Connect");
301
+ if (d.stack.includes("adyen-marketpay"))
302
+ bits.push("Adyen MarketPay");
303
+ if (d.stack.includes("persona") || d.stack.includes("onfido") || d.stack.includes("sumsub"))
304
+ bits.push("KYC vendor");
305
+ return `marketplace / two-sided platform (${bits.join(", ")}) — multi-party payouts + seller KYC + 1099-K + DSA`;
306
+ },
307
+ },
308
+ // ── enterprise-saas (B2B multi-tenant, SSO/SAML/SCIM, audit) ──
309
+ {
310
+ archetype: "enterprise-saas",
311
+ score: (d) => {
312
+ let s = 0;
313
+ if (d.stack.includes("workos"))
314
+ s += 8; // SSO/SCIM-as-a-service
315
+ if (d.stack.includes("auth0"))
316
+ s += 6;
317
+ if (d.stack.includes("okta"))
318
+ s += 6;
319
+ if (d.stack.includes("clerk"))
320
+ s += 4;
321
+ if (d.stack.includes("samlify"))
322
+ s += 6;
323
+ if (d.stack.includes("passport-saml"))
324
+ s += 6;
325
+ if (d.stack.includes("scim"))
326
+ s += 5;
327
+ if (d.readmeKeywords.includes("multi-tenant") || d.readmeKeywords.includes("multitenant"))
328
+ s += 4;
329
+ if (d.readmeKeywords.includes("enterprise") || d.readmeKeywords.includes("b2b"))
330
+ s += 3;
331
+ if (d.readmeKeywords.includes("sso") || d.readmeKeywords.includes("saml"))
332
+ s += 3;
333
+ // Stripe billing + multi-tenant signals = SaaS
334
+ if (s > 0 && d.stack.includes("stripe"))
335
+ s += 1;
336
+ return s;
337
+ },
338
+ reason: (d) => {
339
+ const bits = [];
340
+ if (d.stack.includes("workos"))
341
+ bits.push("WorkOS");
342
+ if (d.stack.includes("auth0"))
343
+ bits.push("Auth0");
344
+ if (d.stack.includes("okta"))
345
+ bits.push("Okta");
346
+ if (d.stack.includes("samlify") || d.stack.includes("passport-saml"))
347
+ bits.push("SAML lib");
348
+ if (d.stack.includes("scim"))
349
+ bits.push("SCIM");
350
+ return `enterprise B2B SaaS (${bits.join(", ")}) — multi-tenant isolation + SSO + audit log + SOC2 mandatory`;
351
+ },
352
+ },
353
+ // ── mlops (model training & lifecycle, distinct from inference) ─
354
+ {
355
+ archetype: "mlops",
356
+ score: (d) => {
357
+ let s = 0;
358
+ if (d.stack.includes("mlflow"))
359
+ s += 7;
360
+ if (d.stack.includes("wandb"))
361
+ s += 6;
362
+ if (d.stack.includes("dvc"))
363
+ s += 6;
364
+ if (d.stack.includes("kubeflow"))
365
+ s += 7;
366
+ if (d.stack.includes("bentoml"))
367
+ s += 5;
368
+ if (d.stack.includes("seldon"))
369
+ s += 5;
370
+ if (d.stack.includes("kserve"))
371
+ s += 5;
372
+ if (d.stack.includes("sagemaker"))
373
+ s += 5;
374
+ if (d.stack.includes("vertex-ai"))
375
+ s += 5;
376
+ if (d.stack.includes("ray"))
377
+ s += 4;
378
+ if (d.stack.includes("torch") || d.stack.includes("tensorflow"))
379
+ s += 2;
380
+ if (d.readmeKeywords.includes("training") && d.readmeKeywords.includes("model"))
381
+ s += 3;
382
+ // De-prioritize if pure inference (LLM API only)
383
+ if (s > 0 && d.stack.includes("anthropic-sdk") && !d.stack.includes("torch") && !d.stack.includes("mlflow"))
384
+ s -= 2;
385
+ return s;
386
+ },
387
+ reason: (d) => {
388
+ const bits = [];
389
+ if (d.stack.includes("mlflow"))
390
+ bits.push("MLflow");
391
+ if (d.stack.includes("wandb"))
392
+ bits.push("W&B");
393
+ if (d.stack.includes("dvc"))
394
+ bits.push("DVC");
395
+ if (d.stack.includes("kubeflow"))
396
+ bits.push("Kubeflow");
397
+ if (d.stack.includes("bentoml"))
398
+ bits.push("BentoML");
399
+ if (d.stack.includes("torch"))
400
+ bits.push("PyTorch");
401
+ if (d.stack.includes("tensorflow"))
402
+ bits.push("TensorFlow");
403
+ return `MLOps stack detected (${bits.join(", ")}) — dataset versioning + drift detection + model registry + EU AI Act high-risk gates`;
404
+ },
405
+ },
406
+ // ── streaming (event-driven / Kafka / Flink — distinct from batch data-platform) ─
407
+ {
408
+ archetype: "streaming",
409
+ score: (d) => {
410
+ let s = 0;
411
+ if (d.stack.includes("kafkajs"))
412
+ s += 7;
413
+ if (d.stack.includes("kafka-node"))
414
+ s += 6;
415
+ if (d.stack.includes("rdkafka"))
416
+ s += 7;
417
+ if (d.stack.includes("kinesis"))
418
+ s += 6;
419
+ if (d.stack.includes("pulsar"))
420
+ s += 6;
421
+ if (d.stack.includes("flink"))
422
+ s += 7;
423
+ if (d.stack.includes("beam"))
424
+ s += 6;
425
+ if (d.stack.includes("debezium"))
426
+ s += 7;
427
+ if (d.stack.includes("nats"))
428
+ s += 5;
429
+ if (d.stack.includes("rabbitmq"))
430
+ s += 4;
431
+ if (d.readmeKeywords.includes("streaming") || d.readmeKeywords.includes("event-driven"))
432
+ s += 2;
433
+ if (d.readmeKeywords.includes("cdc") || d.readmeKeywords.includes("real-time"))
434
+ s += 2;
435
+ return s;
436
+ },
437
+ reason: (d) => {
438
+ const bits = [];
439
+ if (d.stack.includes("kafkajs") || d.stack.includes("rdkafka") || d.stack.includes("kafka-node"))
440
+ bits.push("Kafka");
441
+ if (d.stack.includes("kinesis"))
442
+ bits.push("Kinesis");
443
+ if (d.stack.includes("pulsar"))
444
+ bits.push("Pulsar");
445
+ if (d.stack.includes("flink"))
446
+ bits.push("Flink");
447
+ if (d.stack.includes("beam"))
448
+ bits.push("Beam");
449
+ if (d.stack.includes("debezium"))
450
+ bits.push("Debezium CDC");
451
+ return `streaming / event-driven stack (${bits.join(", ")}) — exactly-once + backpressure + DLQ + schema evolution gates`;
452
+ },
453
+ },
454
+ // ── cms / content platform (headless CMS, publishing, SEO) ────
455
+ {
456
+ archetype: "cms",
457
+ score: (d) => {
458
+ let s = 0;
459
+ if (d.stack.includes("sanity"))
460
+ s += 9;
461
+ if (d.stack.includes("contentful"))
462
+ s += 9;
463
+ if (d.stack.includes("strapi"))
464
+ s += 9;
465
+ if (d.stack.includes("payload"))
466
+ s += 8;
467
+ if (d.stack.includes("ghost"))
468
+ s += 8;
469
+ if (d.stack.includes("gatsby"))
470
+ s += 5;
471
+ if (d.stack.includes("eleventy"))
472
+ s += 5;
473
+ if (d.stack.includes("hugo"))
474
+ s += 5;
475
+ if (d.stack.includes("astro") && d.readmeKeywords.includes("blog"))
476
+ s += 4;
477
+ if (d.stack.includes("next.js") && d.readmeKeywords.includes("blog"))
478
+ s += 3;
479
+ if (d.readmeKeywords.includes("cms") || d.readmeKeywords.includes("publishing") || d.readmeKeywords.includes("blog"))
480
+ s += 2;
481
+ // De-prioritize if it's clearly an enterprise SaaS app
482
+ if (s > 0 && (d.stack.includes("workos") || d.stack.includes("samlify")))
483
+ s -= 3;
484
+ return s;
485
+ },
486
+ reason: (d) => {
487
+ const bits = [];
488
+ if (d.stack.includes("sanity"))
489
+ bits.push("Sanity");
490
+ if (d.stack.includes("contentful"))
491
+ bits.push("Contentful");
492
+ if (d.stack.includes("strapi"))
493
+ bits.push("Strapi");
494
+ if (d.stack.includes("payload"))
495
+ bits.push("Payload");
496
+ if (d.stack.includes("ghost"))
497
+ bits.push("Ghost");
498
+ if (d.stack.includes("gatsby"))
499
+ bits.push("Gatsby");
500
+ if (d.stack.includes("eleventy"))
501
+ bits.push("Eleventy");
502
+ if (d.stack.includes("hugo"))
503
+ bits.push("Hugo");
504
+ return `CMS / publishing stack (${bits.join(", ")}) — schema.org + Core Web Vitals + DMCA + UGC moderation gates`;
505
+ },
506
+ },
267
507
  // ── healthcare (FHIR/HL7/PHI) ───────────────────
268
508
  {
269
509
  archetype: "healthcare",
@@ -286,6 +526,72 @@ const RULES = [
286
526
  return `healthcare data tooling: ${bits.join(", ")} — HIPAA/PHI handling gates required`;
287
527
  },
288
528
  },
529
+ // ── regulated (compliance-first: DORA/NIS2/SOX/FedRAMP — not fintech/healthcare) ──
530
+ {
531
+ archetype: "regulated",
532
+ score: (d) => {
533
+ let s = 0;
534
+ // Compliance automation SaaS installed → near-certain regulated industry
535
+ if (d.stack.includes("compliance-automation"))
536
+ s += 10;
537
+ // Compliance documentation structure
538
+ if (d.stack.includes("compliance-docs"))
539
+ s += 7;
540
+ // Audit log package
541
+ if (d.stack.includes("audit-log"))
542
+ s += 5;
543
+ // README regulatory signals
544
+ const kws = d.readmeKeywords;
545
+ if (kws.includes("regulated"))
546
+ s += 5;
547
+ // FedRAMP / FISMA / CMMC → US federal regulated
548
+ const fedSignals = ["fedramp", "fisma", "cmmc"];
549
+ if (fedSignals.some((k) => kws.includes(k)))
550
+ s += 8;
551
+ // DORA ICT / NIS2 → EU regulated
552
+ if (kws.includes("dora ict") || kws.includes("nis2"))
553
+ s += 6;
554
+ // SOX compliance (non-fintech context)
555
+ if (kws.includes("sox compliance") || kws.includes("sarbanes")) {
556
+ if (!d.stack.includes("plaid") && !d.stack.includes("fintech"))
557
+ s += 5;
558
+ }
559
+ // Generic compliance/audit signals without stronger domain archetype
560
+ if (kws.includes("compliance automation") || kws.includes("audit trail"))
561
+ s += 4;
562
+ if (kws.includes("iso 27001") || kws.includes("soc 2 type"))
563
+ s += 3;
564
+ // Hard exclude: fintech and healthcare have their own dedicated archetypes
565
+ if (d.stack.includes("plaid") || d.stack.includes("wise") || d.stack.includes("fhir"))
566
+ s = 0;
567
+ return s;
568
+ },
569
+ reason: (d) => {
570
+ const bits = [];
571
+ if (d.stack.includes("compliance-automation"))
572
+ bits.push("compliance automation (Vanta/Drata/Secureframe)");
573
+ if (d.stack.includes("compliance-docs"))
574
+ bits.push("ISMS/risk-register/compliance docs");
575
+ if (d.stack.includes("audit-log"))
576
+ bits.push("audit log package");
577
+ const kws = d.readmeKeywords;
578
+ if (kws.includes("regulated"))
579
+ bits.push("regulated-industry README");
580
+ if (kws.includes("fedramp"))
581
+ bits.push("FedRAMP");
582
+ if (kws.includes("fisma"))
583
+ bits.push("FISMA");
584
+ if (kws.includes("cmmc"))
585
+ bits.push("CMMC");
586
+ if (kws.includes("dora ict"))
587
+ bits.push("DORA ICT");
588
+ if (kws.includes("nis2"))
589
+ bits.push("NIS2");
590
+ if (kws.includes("sox compliance") || kws.includes("sarbanes"))
591
+ bits.push("SOX");
592
+ return `regulated-industry signals (${bits.join(", ")}) — DORA/NIS2/SOX/FedRAMP compliance gates required`;
593
+ },
594
+ },
289
595
  // ── cli-tool (explicit CLI: bin field + cli entry) ─
290
596
  {
291
597
  archetype: "cli-tool",
@@ -294,6 +600,9 @@ const RULES = [
294
600
  // Explicit cli marker from detect.ts (bin field present)
295
601
  if (d.stack.includes("cli"))
296
602
  s += 6;
603
+ // Python CLI via [project.scripts] / console_scripts entry
604
+ if (d.stack.includes("python-cli"))
605
+ s += 7;
297
606
  if (d.codeStructure.hasCliEntry)
298
607
  s += 2;
299
608
  if (d.readmeKeywords.includes("cli"))
@@ -490,10 +799,11 @@ const RULES = [
490
799
  // higher in this list (more specific / domain-bound first).
491
800
  const TIE_BREAK_PRIORITY = [
492
801
  "browser-extension", "iot-embedded", "web3", "game",
493
- "agent-product", "fintech", "healthcare",
494
- "commerce", "ai-system", "devtools",
495
- "data-platform", "infra", "mobile-app",
496
- "cli-tool", "web-service", "library", "regulated", "greenfield",
802
+ "agent-product", "fintech", "healthcare", "marketplace",
803
+ "mlops", "streaming",
804
+ "commerce", "enterprise-saas", "ai-system", "devtools",
805
+ "data-platform", "cms", "infra", "mobile-app",
806
+ "regulated", "cli-tool", "web-service", "library", "greenfield",
497
807
  ];
498
808
  function priorityIndex(a) {
499
809
  const i = TIE_BREAK_PRIORITY.indexOf(a);
@@ -588,9 +898,52 @@ export function suggestCompliance(d, archetype) {
588
898
  c.add("soc2-type-2");
589
899
  c.add("gdpr");
590
900
  }
901
+ if (archetype === "regulated") {
902
+ c.add("iso27001");
903
+ c.add("gdpr");
904
+ c.add("compliance-required");
905
+ // FedRAMP/CMMC if US federal signals present
906
+ const kws = d.readmeKeywords;
907
+ if (kws.includes("fedramp") || kws.includes("fisma") || kws.includes("cmmc"))
908
+ c.add("fedramp");
909
+ if (kws.includes("dora ict") || kws.includes("nis2")) {
910
+ c.add("dora-ict");
911
+ c.add("nis2");
912
+ }
913
+ if (kws.includes("sox compliance") || kws.includes("sarbanes"))
914
+ c.add("sox");
915
+ }
591
916
  if (archetype === "web-service")
592
917
  c.add("gdpr"); // baseline for user data
593
918
  if (archetype === "cli-tool") { /* CLI tools usually don't have compliance load */ }
919
+ if (archetype === "marketplace") {
920
+ c.add("pci-dss");
921
+ c.add("kyc-aml");
922
+ c.add("gdpr");
923
+ c.add("dsa-eu");
924
+ c.add("p2b-eu");
925
+ c.add("1099-k");
926
+ }
927
+ if (archetype === "enterprise-saas") {
928
+ c.add("soc2-type-2");
929
+ c.add("iso27001");
930
+ c.add("gdpr");
931
+ c.add("ccpa");
932
+ }
933
+ if (archetype === "mlops") {
934
+ c.add("eu-ai-act");
935
+ c.add("nist-ai-rmf");
936
+ c.add("iso42001");
937
+ }
938
+ if (archetype === "streaming") {
939
+ c.add("gdpr"); // event retention rules
940
+ }
941
+ if (archetype === "cms") {
942
+ c.add("dmca");
943
+ c.add("wcag-2.2");
944
+ c.add("gdpr");
945
+ c.add("dsa-eu");
946
+ }
594
947
  // ── stack-derived (cross-archetype) ──────────────
595
948
  if (d.stack.includes("stripe") || d.stack.includes("braintree") ||
596
949
  d.stack.includes("adyen") || d.stack.includes("paddle")) {
package/dist/detect.js CHANGED
@@ -219,6 +219,89 @@ export function detect(dir) {
219
219
  if (has("@workos-inc/node")) {
220
220
  stack.add("workos");
221
221
  }
222
+ if (has("auth0") || has("@auth0/nextjs-auth0") || has("@auth0/auth0-react"))
223
+ stack.add("auth0");
224
+ if (has("@okta/okta-auth-js") || has("@okta/oidc-middleware"))
225
+ stack.add("okta");
226
+ if (has("samlify"))
227
+ stack.add("samlify");
228
+ if (has("passport-saml") || has("@node-saml/passport-saml"))
229
+ stack.add("passport-saml");
230
+ if (has("@scim2/core") || has("scim2") || has("scim-patch"))
231
+ stack.add("scim");
232
+ // Compliance automation (regulated industry — vanta/drata/secureframe are decisive signals)
233
+ if (has("vanta") || has("@vanta/agent")) {
234
+ stack.add("compliance-automation");
235
+ sig("regulated", "vanta");
236
+ }
237
+ if (has("drata-cli") || has("@drata/sdk")) {
238
+ stack.add("compliance-automation");
239
+ sig("regulated", "drata");
240
+ }
241
+ if (has("secureframe") || has("@secureframe/node")) {
242
+ stack.add("compliance-automation");
243
+ sig("regulated", "secureframe");
244
+ }
245
+ if (has("tugboat-logic")) {
246
+ stack.add("compliance-automation");
247
+ sig("regulated", "tugboat-logic");
248
+ }
249
+ // Audit log packages
250
+ if (has("audit-log") || has("node-audit-logger") || has("winston-audit")) {
251
+ stack.add("audit-log");
252
+ sig("regulated", "audit-log");
253
+ }
254
+ // Marketplace / KYC providers
255
+ if (has("@stripe/connect-iframe-loader") || (has("stripe") && (pkg.name?.includes("market") || pkg.name?.includes("connect")))) {
256
+ stack.add("stripe-connect");
257
+ sig("marketplace", "stripe-connect");
258
+ }
259
+ if (has("@adyen/marketpay"))
260
+ stack.add("adyen-marketpay");
261
+ if (has("persona-react") || has("persona-sdk"))
262
+ stack.add("persona");
263
+ if (has("@onfido/api") || has("onfido-sdk-ui"))
264
+ stack.add("onfido");
265
+ if (has("@sumsub/websdk-react"))
266
+ stack.add("sumsub");
267
+ // CMS / publishing
268
+ if (has("@sanity/client") || has("next-sanity") || has("sanity"))
269
+ stack.add("sanity");
270
+ if (has("contentful") || has("@contentful/rich-text-react-renderer"))
271
+ stack.add("contentful");
272
+ if (has("strapi") || has("@strapi/strapi"))
273
+ stack.add("strapi");
274
+ if (has("payload") || has("@payloadcms/payload"))
275
+ stack.add("payload");
276
+ if (has("@tryghost/content-api") || has("ghost"))
277
+ stack.add("ghost");
278
+ if (has("gatsby"))
279
+ stack.add("gatsby");
280
+ if (has("@11ty/eleventy"))
281
+ stack.add("eleventy");
282
+ // Streaming / messaging
283
+ if (has("kafkajs") || has("@confluentinc/kafka-javascript"))
284
+ stack.add("kafkajs");
285
+ if (has("kafka-node"))
286
+ stack.add("kafka-node");
287
+ if (has("node-rdkafka"))
288
+ stack.add("rdkafka");
289
+ if (has("@aws-sdk/client-kinesis"))
290
+ stack.add("kinesis");
291
+ if (has("pulsar-client"))
292
+ stack.add("pulsar");
293
+ if (has("apache-flink-statefun"))
294
+ stack.add("flink");
295
+ if (has("@google-cloud/dataflow"))
296
+ stack.add("beam");
297
+ if (has("debezium"))
298
+ stack.add("debezium");
299
+ if (has("nats") || has("nats.ws"))
300
+ stack.add("nats");
301
+ if (has("amqplib") || has("amqp-connection-manager"))
302
+ stack.add("rabbitmq");
303
+ // MLOps (Python deps detected via pyproject.toml; here we cover Node bridges + Python signal flag)
304
+ // Most MLOps stacks are Python — detected through pyproject.toml block separately.
222
305
  // Databases / ORMs
223
306
  if (has("prisma") || has("@prisma/client"))
224
307
  stack.add("prisma");
@@ -358,6 +441,68 @@ export function detect(dir) {
358
441
  stack.add("ml");
359
442
  sig("ml", "python");
360
443
  }
444
+ // MLOps lifecycle (training / registry / serving)
445
+ if (ihas("mlflow")) {
446
+ stack.add("mlflow");
447
+ sig("mlops", "mlflow");
448
+ }
449
+ if (ihas("wandb")) {
450
+ stack.add("wandb");
451
+ sig("mlops", "wandb");
452
+ }
453
+ if (ihas("dvc")) {
454
+ stack.add("dvc");
455
+ sig("mlops", "dvc");
456
+ }
457
+ if (ihas("kubeflow") || ihas("kfp")) {
458
+ stack.add("kubeflow");
459
+ sig("mlops", "kubeflow");
460
+ }
461
+ if (ihas("bentoml")) {
462
+ stack.add("bentoml");
463
+ sig("mlops", "bentoml");
464
+ }
465
+ if (ihas("seldon-core")) {
466
+ stack.add("seldon");
467
+ sig("mlops", "seldon");
468
+ }
469
+ if (ihas("kserve")) {
470
+ stack.add("kserve");
471
+ sig("mlops", "kserve");
472
+ }
473
+ if (ihas("sagemaker")) {
474
+ stack.add("sagemaker");
475
+ sig("mlops", "sagemaker");
476
+ }
477
+ if (ihas("google-cloud-aiplatform") || ihas("vertex")) {
478
+ stack.add("vertex-ai");
479
+ sig("mlops", "vertex");
480
+ }
481
+ if (ihas("ray[")) {
482
+ stack.add("ray");
483
+ sig("mlops", "ray");
484
+ }
485
+ // Streaming (Python)
486
+ if (ihas("confluent-kafka") || ihas("kafka-python")) {
487
+ stack.add("kafkajs");
488
+ sig("streaming", "kafka-py");
489
+ }
490
+ if (ihas("pyflink") || ihas("apache-flink")) {
491
+ stack.add("flink");
492
+ sig("streaming", "flink-py");
493
+ }
494
+ if (ihas("apache-beam")) {
495
+ stack.add("beam");
496
+ sig("streaming", "beam-py");
497
+ }
498
+ if (ihas("nats-py")) {
499
+ stack.add("nats");
500
+ sig("streaming", "nats-py");
501
+ }
502
+ if (ihas("debezium")) {
503
+ stack.add("debezium");
504
+ sig("streaming", "debezium");
505
+ }
361
506
  if (ihas("opencv") || ihas("ultralytics") || ihas("detectron2")) {
362
507
  stack.add("computer-vision");
363
508
  sig("ai", "computer-vision");
@@ -394,7 +539,15 @@ export function detect(dir) {
394
539
  existsSync(join(dir, "main.py")) ||
395
540
  existsSync(join(dir, "app.py")) ||
396
541
  existsSync(join(dir, "wsgi.py"));
397
- if (isPyLib && !hasPyApp) {
542
+ // Python CLI signal: pyproject [project.scripts] OR setup.py with entry_points={'console_scripts'}
543
+ const hasPyCliEntry = pyproject.includes("[project.scripts]") ||
544
+ pyproject.includes("[tool.poetry.scripts]") ||
545
+ (existsSync(join(dir, "setup.py")) && readFileSync(join(dir, "setup.py"), "utf-8").includes("console_scripts"));
546
+ if (hasPyCliEntry) {
547
+ stack.add("python-cli");
548
+ sig("cli", "python-script");
549
+ }
550
+ if (isPyLib && !hasPyApp && !hasPyCliEntry) {
398
551
  stack.add("library");
399
552
  sig("library", "python");
400
553
  }
@@ -768,6 +921,15 @@ export function detect(dir) {
768
921
  }
769
922
  // ── Project size estimate (Wave 3 lite) ──────────────────
770
923
  const projectSize = estimateProjectSize(dir);
924
+ // ── Compliance / regulated-industry docs (Wave 2 supplement) ──
925
+ const complianceFiles = ["ISMS.md", "isms.md", "risk-register.md", "DORA.md", "NIS2.md",
926
+ "bcp.md", "BCP.md", "control-matrix.md", "security-policy.md"];
927
+ if (complianceFiles.some((f) => existsSync(join(dir, f))) ||
928
+ existsSync(join(dir, "compliance")) ||
929
+ existsSync(join(dir, "controls"))) {
930
+ stack.add("compliance-docs");
931
+ sig("regulated", "compliance-docs");
932
+ }
771
933
  // ── README mining (Wave 2) ───────────────────────────────
772
934
  const readmeKeywords = mineReadmeKeywords(dir);
773
935
  for (const kw of readmeKeywords)
@@ -872,7 +1034,10 @@ function mineReadmeKeywords(dir) {
872
1034
  "cli": ["command-line", "command line", "cli ", "$ npx ", "subcommand"],
873
1035
  "data": ["pipeline", "etl ", "warehouse", "dbt", "airflow", "dataset"],
874
1036
  "game": ["game ", "gameplay", "player", "level design"],
875
- "regulated": ["pci", "hipaa", "soc2", "iso 27001", "gdpr", "compliance"],
1037
+ "regulated": ["dora ict", "nis2", "fedramp", "fisma", "cmmc", "sox compliance",
1038
+ "sarbanes", "regulated entity", "regulated industry",
1039
+ "compliance automation", "audit trail", "iso 27001",
1040
+ "pci compliance", "soc 2 type", "compliance officer"],
876
1041
  };
877
1042
  for (const [bucket, terms] of Object.entries(buckets)) {
878
1043
  if (terms.some((t) => text.includes(t)))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "great-cto",
3
- "version": "1.0.169",
3
+ "version": "1.1.0",
4
4
  "description": "One command install for the great_cto Claude Code plugin. Auto-detects your stack, picks the right archetype, bootstraps PROJECT.md.",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -43,14 +43,26 @@
43
43
  "startup-tools",
44
44
  "solo-founder"
45
45
  ],
46
- "homepage": "https://github.com/avelikiy/great_cto",
46
+ "homepage": "https://greatcto.systems",
47
47
  "license": "MIT",
48
- "author": "avelikiy",
48
+ "author": {
49
+ "name": "Alexander Velikiy",
50
+ "url": "https://hashnode.com/@Greatcto"
51
+ },
49
52
  "repository": {
50
53
  "type": "git",
51
54
  "url": "git+https://github.com/avelikiy/great_cto.git",
52
55
  "directory": "packages/cli"
53
56
  },
57
+ "bugs": {
58
+ "url": "https://github.com/avelikiy/great_cto/issues"
59
+ },
60
+ "funding": [
61
+ {
62
+ "type": "github",
63
+ "url": "https://github.com/sponsors/avelikiy"
64
+ }
65
+ ],
54
66
  "bin": {
55
67
  "great-cto": "index.mjs"
56
68
  },
@@ -63,6 +75,7 @@
63
75
  "scripts": {
64
76
  "build": "tsc",
65
77
  "test": "npm run build && node --test tests/*.test.mjs",
78
+ "test:e2e": "npm run build && node ../../tests/run-archetype-e2e.mjs",
66
79
  "prepublishOnly": "npm run build"
67
80
  },
68
81
  "devDependencies": {