hive-lite 0.1.8 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/lib/health.js CHANGED
@@ -59,6 +59,13 @@ function patternFrom(value) {
59
59
  return normalizePath((value.pattern || value.path || '').trim());
60
60
  }
61
61
 
62
+ function arrayFrom(value) {
63
+ if (Array.isArray(value)) return value.map((item) => String(item || '')).filter(Boolean);
64
+ if (value == null || value === '') return [];
65
+ if (typeof value === 'object') return [];
66
+ return [String(value)];
67
+ }
68
+
62
69
  function readYamlHealth(root, name, fallback, findings) {
63
70
  const file = mapFile(root, name);
64
71
  if (!exists(file)) {
@@ -440,6 +447,206 @@ function rawScope(area) {
440
447
  };
441
448
  }
442
449
 
450
+ function flattenReferenceValues(value) {
451
+ if (!value) return [];
452
+ if (Array.isArray(value)) return value;
453
+ if (typeof value === 'object' && (value.path || value.pattern)) return [value];
454
+ if (typeof value === 'object') return Object.values(value).flatMap((item) => flattenReferenceValues(item));
455
+ return [value];
456
+ }
457
+
458
+ function readableReferences(area) {
459
+ const scope = area.scope || {};
460
+ return flattenReferenceValues(scope.readable_reference || area.readable_reference || [])
461
+ .map((item) => ({
462
+ path: patternFrom(item),
463
+ peerIdentity: item && typeof item === 'object' ? item.peer_identity || item.peerIdentity || item.provider || null : null,
464
+ artifact: item && typeof item === 'object' ? item.artifact || item.artifact_family || item.artifactFamily || null : null,
465
+ }))
466
+ .filter((item) => item.path);
467
+ }
468
+
469
+ function configuredArtifactFamilies(area) {
470
+ const raw = area && area.artifact_families ? area.artifact_families : null;
471
+ if (!raw) return [];
472
+ if (Array.isArray(raw)) {
473
+ return raw.map((item) => ({
474
+ id: item.id || item.name || '',
475
+ createRequires: arrayFrom(item.create_requires || item.createRequires),
476
+ hooks: arrayFrom(item.hooks),
477
+ })).filter((item) => item.id);
478
+ }
479
+ return Object.entries(raw).map(([id, value]) => ({
480
+ id,
481
+ createRequires: arrayFrom(value && (value.create_requires || value.createRequires)),
482
+ hooks: arrayFrom(value && value.hooks),
483
+ }));
484
+ }
485
+
486
+ function configuredPeerFileTemplateCount(area) {
487
+ const raw = area && (area.peer_file_templates || area.peerFileTemplates) ? area.peer_file_templates || area.peerFileTemplates : null;
488
+ if (!raw) return 0;
489
+ if (Array.isArray(raw)) return raw.length;
490
+ return Object.keys(raw).length;
491
+ }
492
+
493
+ function patternArtifactMatches(item, artifact) {
494
+ if (!artifact) return false;
495
+ return item.artifact === artifact || item.artifactFamily === artifact || item.artifact_family === artifact;
496
+ }
497
+
498
+ function areaLooksPeerBased(area) {
499
+ const text = [
500
+ area.id,
501
+ area.name,
502
+ area.description,
503
+ ...(area.aliases || []),
504
+ ...(area.concepts || []),
505
+ ].filter(Boolean).join(' ').toLowerCase();
506
+ return /\b(provider|proxy|adapter|integration|connector|plugin|peer)\b/.test(text)
507
+ || /提供商|代理|适配器|集成|连接器|插件/.test(text);
508
+ }
509
+
510
+ function rawWritableCreatePatterns(area) {
511
+ const scope = area.scope || {};
512
+ return scope.writable_create_patterns || [];
513
+ }
514
+
515
+ function validateCreatePatternMetadata(area, findings) {
516
+ for (const value of rawWritableCreatePatterns(area)) {
517
+ const pattern = patternFrom(value);
518
+ if (!pattern || typeof value !== 'object') {
519
+ addFinding(findings, {
520
+ severity: 'warning',
521
+ code: 'CREATE_PATTERN_METADATA_INCOMPLETE',
522
+ areaId: area.id,
523
+ field: field(area.id, 'scope.writable_create_patterns'),
524
+ message: `Create pattern uses shorthand without operation, artifact, or intent metadata: ${pattern || '(missing pattern)'}.`,
525
+ impact: 'writePlan can infer a create permission, but the map cannot explain which artifact and intent this permission certifies.',
526
+ fix: 'Convert this to an object with pattern, artifact, operations: [create_file], and intent_kinds.',
527
+ });
528
+ continue;
529
+ }
530
+ const operations = arrayFrom(value.operations || value.operation);
531
+ const intentKinds = arrayFrom(value.intent_kinds || value.intentKinds);
532
+ if (!operations.includes('create_file')) {
533
+ addFinding(findings, {
534
+ severity: 'warning',
535
+ code: 'CREATE_PATTERN_OPERATION_UNSPECIFIED',
536
+ areaId: area.id,
537
+ field: field(area.id, 'scope.writable_create_patterns.operations'),
538
+ message: `Create pattern does not explicitly declare the create_file operation: ${pattern}.`,
539
+ impact: 'Required write coverage is less explainable and future certificate output may be ambiguous.',
540
+ fix: 'Add operations: [create_file].',
541
+ });
542
+ }
543
+ if (!value.artifact && !value.artifact_family && !value.artifactFamily) {
544
+ addFinding(findings, {
545
+ severity: 'warning',
546
+ code: 'CREATE_PATTERN_ARTIFACT_UNSPECIFIED',
547
+ areaId: area.id,
548
+ field: field(area.id, 'scope.writable_create_patterns.artifact'),
549
+ message: `Create pattern does not declare the artifact it creates: ${pattern}.`,
550
+ impact: 'Hive Lite cannot explain which required artifact this create permission covers.',
551
+ fix: 'Add artifact such as service_impl, controller, dto, migration, or test.',
552
+ });
553
+ }
554
+ if (intentKinds.length === 0) {
555
+ addFinding(findings, {
556
+ severity: 'warning',
557
+ code: 'CREATE_PATTERN_INTENT_KINDS_UNSPECIFIED',
558
+ areaId: area.id,
559
+ field: field(area.id, 'scope.writable_create_patterns.intent_kinds'),
560
+ message: `Create pattern does not declare the intent kinds it supports: ${pattern}.`,
561
+ impact: 'The create permission may apply to more intents than intended.',
562
+ fix: 'Add intent_kinds such as add_provider_proxy or add_endpoint.',
563
+ });
564
+ }
565
+ }
566
+ }
567
+
568
+ function validateCapabilityLedger(root, area, findings) {
569
+ const normalized = normalizeAreaScope(root, area);
570
+ const raw = rawScope(area);
571
+ const references = readableReferences(area);
572
+ const updateWritable = [
573
+ ...raw.direct.map((value) => ({ pattern: patternFrom(value), source: 'writable_direct' })),
574
+ ...normalized.writableExisting.map((item) => ({ pattern: patternFrom(item), source: 'writable_existing' })),
575
+ ].filter((item) => item.pattern);
576
+
577
+ for (const reference of references) {
578
+ const conflict = updateWritable.find((item) => matchesPattern(reference.path, item.pattern));
579
+ if (conflict) {
580
+ addFinding(findings, {
581
+ severity: 'warning',
582
+ code: 'REFERENCE_WRITABLE_SCOPE_CONFLICT',
583
+ areaId: area.id,
584
+ field: field(area.id, 'scope.readable_reference'),
585
+ message: `Reference-only file also matches update-capable writable scope: ${reference.path}.`,
586
+ impact: 'A peer example or copy source may be treated as editable for an intent where it should only guide implementation.',
587
+ fix: 'Keep peer examples under readable_reference only, or remove the conflict by using writable_create_patterns for new-peer files.',
588
+ metrics: { writableScope: conflict.pattern, writableSource: conflict.source },
589
+ });
590
+ }
591
+ }
592
+
593
+ const families = configuredArtifactFamilies(area);
594
+ for (const family of families) {
595
+ for (const artifact of family.createRequires) {
596
+ if (!normalized.writableCreatePatterns.some((item) => patternArtifactMatches(item, artifact))) {
597
+ addFinding(findings, {
598
+ severity: 'warning',
599
+ code: 'ARTIFACT_CREATE_PATTERN_MISSING',
600
+ areaId: area.id,
601
+ field: field(area.id, `artifact_families.${family.id}.create_requires`),
602
+ message: `Artifact family ${family.id} declares create artifact ${artifact}, but no writable_create_patterns entry covers it.`,
603
+ impact: 'Create intents for this family may stay in discovery_context until the map records narrow create permission.',
604
+ fix: `Add a narrow scope.writable_create_patterns entry with artifact: ${artifact}.`,
605
+ });
606
+ }
607
+ }
608
+ for (const hook of family.hooks) {
609
+ if (!normalized.writableExisting.some((item) => patternArtifactMatches(item, hook))) {
610
+ addFinding(findings, {
611
+ severity: 'warning',
612
+ code: 'ARTIFACT_HOOK_SCOPE_MISSING',
613
+ areaId: area.id,
614
+ field: field(area.id, `artifact_families.${family.id}.hooks`),
615
+ message: `Artifact family ${family.id} declares required hook ${hook}, but no writable_existing entry covers it.`,
616
+ impact: 'Create intents that also need an existing registry/router/index update may not receive a complete edit permit.',
617
+ fix: `Add an exact scope.writable_existing entry with artifact: ${hook}.`,
618
+ });
619
+ }
620
+ }
621
+ }
622
+
623
+ validateCreatePatternMetadata(area, findings);
624
+
625
+ const peerExamples = references.filter((item) => item.peerIdentity || item.artifact).length;
626
+ if (areaLooksPeerBased(area) && peerExamples > 0 && normalized.writableCreatePatterns.length === 0) {
627
+ addFinding(findings, {
628
+ severity: 'warning',
629
+ code: 'PEER_EXAMPLES_WITHOUT_CREATE_PATTERNS',
630
+ areaId: area.id,
631
+ field: field(area.id, 'scope.readable_reference'),
632
+ message: 'Peer/example files are mapped, but the area has no create capability.',
633
+ impact: 'New peer/provider/adapter intents can use examples for context but still lack certified create permission.',
634
+ fix: 'Keep peer examples under readable_reference and add narrow scope.writable_create_patterns for new peer artifacts.',
635
+ });
636
+ }
637
+ if (configuredPeerFileTemplateCount(area) > 0 && normalized.writableCreatePatterns.length === 0) {
638
+ addFinding(findings, {
639
+ severity: 'warning',
640
+ code: 'PEER_TEMPLATES_WITHOUT_CREATE_PATTERNS',
641
+ areaId: area.id,
642
+ field: field(area.id, 'peer_file_templates'),
643
+ message: 'peer_file_templates are configured, but the area has no create capability.',
644
+ impact: 'Templates can explain candidate files but cannot grant create permission.',
645
+ fix: 'Add narrow scope.writable_create_patterns for templates that represent allowed new files.',
646
+ });
647
+ }
648
+ }
649
+
443
650
  function validateWritableScope(root, tracked, area, findings) {
444
651
  const normalized = normalizeAreaScope(root, area);
445
652
  const raw = rawScope(area);
@@ -957,6 +1164,7 @@ function evaluateMapHealth(root, options = {}) {
957
1164
  validateFindTerms(area, aliasOwners, findings);
958
1165
  validateEntryPoints(root, area, findings);
959
1166
  validateWritableScope(root, tracked, area, findings);
1167
+ validateCapabilityLedger(root, area, findings);
960
1168
  validateValidation(area, profilesById, scripts, findings);
961
1169
  validateVerification(area, profilesById, findings);
962
1170
  validateRisk(area, findings);