Class: Project

Inherits:
Object
  • Object
show all
Defined in:
lib/almirah/project.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(configuration) ⇒ Project

Returns a new instance of Project.



19
20
21
22
23
24
25
26
27
# File 'lib/almirah/project.rb', line 19

def initialize(configuration)
  @configuration = configuration
  @project_data = ProjectData.new

  @index = nil
  @project = self
  FileUtils.remove_dir("#{@configuration.project_root_directory}/build", true)
  copy_resources
end

Instance Attribute Details

#configurationObject

Returns the value of attribute configuration.



17
18
19
# File 'lib/almirah/project.rb', line 17

def configuration
  @configuration
end

#indexObject

Returns the value of attribute index.



17
18
19
# File 'lib/almirah/project.rb', line 17

def index
  @index
end

#projectObject

Returns the value of attribute project.



17
18
19
# File 'lib/almirah/project.rb', line 17

def project
  @project
end

#project_dataObject

Returns the value of attribute project_data.



17
18
19
# File 'lib/almirah/project.rb', line 17

def project_data
  @project_data
end

Instance Method Details

#add_to_decision_group(doc, rel_dir) ⇒ Object

Add a decision record to its planning group, keyed on the first-level folder under decisions/ (a record directly under decisions/ has rel_dir '.', kept as its own '.' group rather than dropped). Groups are single-key hashes appended in folder-encounter order; the matching one is reused. See ADR-197.



316
317
318
319
320
321
322
323
324
# File 'lib/almirah/project.rb', line 316

def add_to_decision_group(doc, rel_dir)
  group_name = rel_dir.split('/').first
  group = @project_data.decision_groups.find { |g| g.key?(group_name) }
  if group.nil?
    @project_data.decision_groups.append({ group_name => [doc] })
  else
    group[group_name].append(doc)
  end
end

#add_to_risk_registry(doc) ⇒ Object

Add a risk record to its registry, keyed on the first-level folder under risks/. Registries are single-key hashes appended in folder-encounter order, mirroring add_to_decision_group.



374
375
376
377
378
379
380
381
# File 'lib/almirah/project.rb', line 374

def add_to_risk_registry(doc)
  registry = @project_data.risk_registries.find { |g| g.key?(doc.registry) }
  if registry.nil?
    @project_data.risk_registries.append({ doc.registry => [doc] })
  else
    registry[doc.registry].append(doc)
  end
end

#aligned_work_item(target, activity) ⇒ Object

The target record's work item whose activity (Item) matches activity, falling back to the nearest earlier activity by canonical phase order, then (when the target has only later activities) to its earliest row. nil only when the target has no Scope rows.



188
189
190
191
192
193
194
195
196
197
198
199
200
# File 'lib/almirah/project.rb', line 188

def aligned_work_item(target, activity)
  items = target.scope_work_items
  return nil if items.empty?

  exact = items.select { |t| t.activity == activity }.min_by(&:step)
  return exact if exact

  rank = WorkItem::ACTIVITY_ORDER.index(activity) || WorkItem::ACTIVITY_ORDER.length
  earlier = items.select { |t| t.activity_rank <= rank }
  return items.min_by { |t| [t.activity_rank, t.step] } if earlier.empty?

  earlier.min_by { |t| [-t.activity_rank, t.step] }
end

Assigns each document its generated output path (relative to the build root) and registers it for cross-document link resolution (ADR-186). Runs after all documents are parsed and before any rendering, so link targets are known.



235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
# File 'lib/almirah/project.rb', line 235

def build_link_registry
  reg = @project_data.link_registry
  TextLine.link_registry = reg
  TextLine.reset_broken_links
  @project_data.specifications.each do |d|
    d.output_rel_path = "specifications/#{d.id}/#{d.id}.html"
    reg.register(d)
  end
  @project_data.protocols.each do |d|
    d.output_rel_path = "tests/protocols/#{d.id}/#{d.id}.html"
    reg.register(d)
  end
  @project_data.decisions.each do |d|
    d.output_rel_path = "decisions/#{d.html_rel_path}"
    reg.register(d)
  end
  @project_data.risk_records.each do |d|
    d.output_rel_path = "risks/#{d.html_rel_path}"
    reg.register(d)
  end
  @project_data.source_files.each do |d|
    rel = d.path.sub("#{d.root_path}/", '')
    d.output_rel_path = "source_files/#{d.repository}/#{rel}.html"
    reg.register(d)
  end
end

#check_wrong_specification_referencedObject



481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
# File 'lib/almirah/project.rb', line 481

def check_wrong_specification_referenced
  available_specification_ids = {}

  @project_data.specifications.each do |s|
    available_specification_ids[s.id.to_s.downcase] = s
  end

  @project_data.specifications.each do |s| # rubocop:disable Style/CombinableLoops
    s.up_link_docs.each do |key, _value|
      next if available_specification_ids.key?(key)

      # now key points to the doc_id that does not exist
      wrong_doc_id = key
      # find the item that reference to it
      s.controlled_items.each do |item|
        next if item.up_link_ids.nil?

        item.up_link_ids.each do |up_link_id|
          next unless tmp = /^([a-zA-Z]+)-\d+/.match(up_link_id) # SRS

          if tmp[1].downcase == wrong_doc_id
            # we got it finally!
            s.wrong_links_hash[up_link_id.to_s] = item
          end
        end
      end
    end
  end
end

#copy_resourcesObject



29
30
31
32
33
34
35
36
37
38
39
40
41
# File 'lib/almirah/project.rb', line 29

def copy_resources
  # scripts
  gem_root = File.expand_path './../..', File.dirname(__FILE__)
  src_folder = "#{gem_root}/lib/almirah/templates/scripts"
  dst_folder = "#{@configuration.project_root_directory}/build/scripts"
  FileUtils.mkdir_p(dst_folder)
  FileUtils.copy_entry(src_folder, dst_folder)
  # css
  src_folder = "#{gem_root}/lib/almirah/templates/css"
  dst_folder = "#{@configuration.project_root_directory}/build/css"
  FileUtils.mkdir_p(dst_folder)
  FileUtils.copy_entry(src_folder, dst_folder)
end

#create_indexObject



548
549
550
# File 'lib/almirah/project.rb', line 548

def create_index
  @index = Index.new(@project)
end

#create_search_dataObject



699
700
701
702
703
704
# File 'lib/almirah/project.rb', line 699

def create_search_data
  db = SpecificationsDb.new @project_data.specifications
  data_path = "#{@configuration.project_root_directory}/build/data"
  FileUtils.mkdir_p(data_path)
  db.save(data_path)
end

#decision_group_name(doc) ⇒ Object

The planning-group name (first-level decisions/ folder) a record belongs to, read from the decision_groups collection (ADR-197).



204
205
206
207
# File 'lib/almirah/project.rb', line 204

def decision_group_name(doc)
  group = @project_data.decision_groups.find { |g| g.values.first.include?(doc) }
  group&.keys&.first
end


441
442
443
444
445
446
447
448
449
450
451
452
# File 'lib/almirah/project.rb', line 441

def link_all_decisions
  number_of_links = 0
  @project_data.decisions.each do |d|
    @project_data.specifications.each do |s|
      next unless d.up_link_docs.key?(s.id.to_s)

      DocLinker.link_decision_to_spec(d, s)
      number_of_links += 1
    end
  end
  ConsoleReporter.count('decision links', number_of_links)
end


424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
# File 'lib/almirah/project.rb', line 424

def link_all_protocols
  @project_data.protocols.each do |p|
    @project_data.specifications.each do |s|
      if p.up_link_docs.key?(s.id.to_s)
        DocLinker.link_protocol_to_spec(p, s)
        @project_data.covered_specifications_dictionary[s.id.to_s] = s
      end
    end
  end
  # create coverage documents
  @project_data.covered_specifications_dictionary.each do |_key, value|
    doc = DocFabric.create_coverage_matrix(value)
    @project_data.coverage_matrices.append doc
  end
  ConsoleReporter.count('coverage matrices', @project_data.coverage_matrices.length)
end

A risk record's Affected Documents uplinks resolve exactly as a decision record's (ADR-218): the specification paragraph gains the record among its downlinks and a dangling Req-ID lands in the record's wrong_links_hash.



457
458
459
460
461
462
463
464
465
466
467
468
# File 'lib/almirah/project.rb', line 457

def link_all_risks
  number_of_links = 0
  @project_data.risk_records.each do |r|
    @project_data.specifications.each do |s|
      next unless r.up_link_docs.key?(s.id.to_s)

      DocLinker.link_decision_to_spec(r, s)
      number_of_links += 1
    end
  end
  ConsoleReporter.count('risk links', number_of_links)
end


470
471
472
473
474
475
476
477
478
479
# File 'lib/almirah/project.rb', line 470

def link_all_source_files
  return unless DocLinker.link_all_source_files(@project_data)

  # create implementation documents
  @project_data.implemented_specifications_dictionary.each do |_key, value|
    doc = DocFabric.create_implementation_document(value)
    @project_data.implementation_matrices.append doc
  end
  ConsoleReporter.count('implementation matrices', @project_data.implementation_matrices.length)
end


405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
# File 'lib/almirah/project.rb', line 405

def link_all_specifications
  comb_list = @project_data.specifications.combination(2)
  comb_list.each do |c|
    link_two_specifications(c[0], c[1])
    # puts "Link: #{c[0].id} - #{c[1].id}"
  end
  # separatelly create design inputs treceability
  @configuration.get_design_inputs.each do |i|
    next unless @project_data.specifications_dictionary.key? i.to_s.downcase

    document = @project_data.specifications_dictionary[i.to_s.downcase]
    if document
      doc = DocFabric.create_traceability_document(document, nil)
      @project_data.traceability_matrices.append doc
    end
  end
  ConsoleReporter.count('traceability matrices', @project_data.traceability_matrices.length)
end


160
161
162
163
164
165
166
# File 'lib/almirah/project.rb', line 160

def link_cross_record_dependencies
  @project_data.decisions.each do |d|
    d.scope_work_items.each do |wi|
      wi.depends_on_refs.each { |ref| link_dependency(d, wi, ref) }
    end
  end
end


168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/almirah/project.rb', line 168

def link_dependency(record, work_item, ref)
  target = @project_data.link_registry.find_by_id(ref)
  unless target.is_a?(Decision)
    @kit_unresolved << { record: record.id, target: ref }
    return
  end
  prereq = aligned_work_item(target, work_item.activity)
  return if prereq.nil? || prereq.equal?(work_item)

  cross = decision_group_name(record) != decision_group_name(target)
  work_item.add_predecessor(prereq, cross_group: cross)
  prereq.add_successor(work_item)
  anchor = target.scope_table&.step_column? ? prereq.row_anchor : nil
  work_item.add_resolved_dependency(ref, target, anchor, prereq.id)
end

Each row's lower-numbered same-record steps are its predecessors; equal step numbers are concurrent (no edge). These edges are in-group by definition.



146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/almirah/project.rb', line 146

def link_intra_record_steps
  @project_data.decisions.each do |d|
    items = d.scope_work_items
    items.each do |wi|
      items.each do |other|
        next if other.equal?(wi) || other.step >= wi.step

        wi.add_predecessor(other, cross_group: false)
        other.add_successor(wi)
      end
    end
  end
end


511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
# File 'lib/almirah/project.rb', line 511

def link_two_specifications(doc_a, doc_b)
  if doc_b.up_link_docs.key?(doc_a.id.to_s)
    top_document = doc_a
    bottom_document = doc_b
  elsif doc_a.up_link_docs.key?(doc_b.id.to_s)
    top_document = doc_b
    bottom_document = doc_a
  else
    return # no links
  end
  # puts "Link: #{doc_a.id} - #{doc_b.id}"
  bottom_document.controlled_items.each do |item|
    next unless item.up_link_ids

    item.up_link_ids.each do |up_lnk|
      if top_document.dictionary.key?(up_lnk.to_s)

        top_item = top_document.dictionary[up_lnk.to_s]

        unless top_item.down_links
          top_item.down_links = []
          top_document.items_with_downlinks_number += 1 # for statistics
        end
        top_item.down_links.append(item)
      elsif tmp = /^([a-zA-Z]+)-\d+/.match(up_lnk)
        # check if there is a non existing link with the right doc_id
        if tmp[1].downcase == top_document.id.downcase
          bottom_document.wrong_links_hash[up_lnk] = item
        end # SRS
      end
    end
  end
  # create treceability document
  doc = DocFabric.create_traceability_document(top_document, bottom_document)
  @project_data.traceability_matrices.append doc
end

Builds the per-row WorkItem dependency network (ADR-194): registers every Scope-row work item, fills the intra-record step-order edges, then resolves each Depends On reference globally (LinkRegistry) to the activity-type-aligned work item of the target record, tagging each cross-record edge in-group or cross-group against the decision_groups boundary (ADR-197). Unresolved references are collected for report_kit_violations. Runs after build_link_registry (so every record resolves) and before rendering (so the overview Kit column is ready).



135
136
137
138
139
140
141
142
# File 'lib/almirah/project.rb', line 135

def link_work_items
  @kit_unresolved = []
  @project_data.decisions.each do |d|
    d.scope_work_items.each { |wi| @project_data.work_items[wi.id] = wi }
  end
  link_intra_record_steps
  link_cross_record_dependencies
end

#parse_all_protocolsObject



272
273
274
275
276
277
278
279
# File 'lib/almirah/project.rb', line 272

def parse_all_protocols
  path = @configuration.project_root_directory
  Dir.glob("#{path}/tests/protocols/**/*.md").each do |f|
    doc = DocFabric.create_protocol(f)
    @project_data.protocols.append(doc)
  end
  ConsoleReporter.count('parsing test protocols', @project_data.protocols.length)
end

#parse_all_source_filesObject



281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
# File 'lib/almirah/project.rb', line 281

def parse_all_source_files
  @configuration.get_repositories.each do |repos|
    # puts "Processing repository: #{repos['name']}, #{repos['path']}"
    next unless repos['path'] && Dir.exist?(repos['path'])

    file_path = repos['path']
    Dir.glob("#{repos['path']}/**/*.*").each do |f|
      next unless File.file?(f) && f.end_with?('.c', '.cpp', '.h', '.hpp', '.py', '.java', '.rb', '.js', '.ts', '.go',
                                               '.rs')

      doc = DocFabric.create_source_file(file_path, f, repos['name'])
      # puts "Source file: #{doc.id}"
      @project_data.source_files.append(doc)
    end
  end
end

#parse_all_specificationsObject



262
263
264
265
266
267
268
269
270
# File 'lib/almirah/project.rb', line 262

def parse_all_specifications
  path = @configuration.project_root_directory
  Dir.glob("#{path}/specifications/**/*.md").each do |f|
    doc = DocFabric.create_specification(f)
    @project_data.specifications.append(doc)
    @project_data.specifications_dictionary[doc.id.to_s.downcase] = doc
  end
  ConsoleReporter.count('parsing specifications', @project_data.specifications.length)
end

#parse_decisionsObject



298
299
300
301
302
303
304
305
306
307
308
309
310
# File 'lib/almirah/project.rb', line 298

def parse_decisions
  path = @configuration.project_root_directory
  decisions_root = "#{path}/decisions"
  Dir.glob("#{decisions_root}/**/*.md").each do |f|
    doc = DocFabric.create_decision(f)
    rel_dir = File.dirname(f.sub("#{decisions_root}/", ''))
    doc.html_rel_path = rel_dir == '.' ? "#{doc.id}.html" : "#{rel_dir}/#{doc.id}.html"
    @project_data.decisions.append(doc)
    add_to_decision_group(doc, rel_dir)
  end
  BaseDocument.show_decisions_link = @project_data.decisions.any?
  ConsoleReporter.count('parsing decisions', @project_data.decisions.length)
end

#parse_risksObject

Collect risk records (ADR-215): each first-level subfolder of risks/ is a risk registry; a registry's overview.md is its preface, not a record. Files directly under risks/ belong to no registry and are not collected.



329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
# File 'lib/almirah/project.rb', line 329

def parse_risks
  path = @configuration.project_root_directory
  risks_root = "#{path}/risks"
  Dir.glob("#{risks_root}/*/**/*.md").each do |f|
    if File.basename(f).downcase == 'overview.md'
      register_risk_preface(f, risks_root)
      next
    end

    doc = DocFabric.create_risk_record(f)
    rel_dir = File.dirname(f.sub("#{risks_root}/", ''))
    doc.registry = rel_dir.split('/').first
    doc.html_rel_path = "#{rel_dir}/#{doc.id}.html"
    @project_data.risk_records.append(doc)
    add_to_risk_registry(doc)
  end
  BaseDocument.show_risks_link = risk_registry_names.any?
  ConsoleReporter.count('parsing risk records', @project_data.risk_records.length)
  report_duplicate_risk_ids
end

#parse_test_run(test_run) ⇒ Object



397
398
399
400
401
402
403
# File 'lib/almirah/project.rb', line 397

def parse_test_run(test_run)
  path = @configuration.project_root_directory
  Dir.glob("#{path}/tests/runs/#{test_run}/**/*.md").each do |f|
    doc = DocFabric.create_protocol(f)
    @project_data.protocols.append(doc)
  end
end

#register_risk_preface(file, risks_root) ⇒ Object

A registry's own overview.md is its preface (ADR-216), parsed like a record for rendering but never collected. An overview.md nested deeper inside a registry is neither a record nor a preface and is skipped entirely.



361
362
363
364
365
366
367
368
369
# File 'lib/almirah/project.rb', line 361

def register_risk_preface(file, risks_root)
  rel_dir = File.dirname(file.sub("#{risks_root}/", ''))
  return unless rel_dir.index('/').nil?

  doc = DocFabric.create_risk_record(file)
  doc.registry = rel_dir
  doc.output_rel_path = "risks/#{rel_dir}/overview.html"
  @project_data.risk_registry_prefaces[rel_dir] = doc
end

#render_all_decisionsObject



624
625
626
627
628
629
630
631
632
633
634
635
636
637
# File 'lib/almirah/project.rb', line 624

def render_all_decisions
  return if @project_data.decisions.empty?

  build_decisions_root = "#{@configuration.project_root_directory}/build/decisions"
  @project_data.decisions.each do |doc|
    out_dir_rel = File.dirname(doc.html_rel_path)
    out_dir = out_dir_rel == '.' ? build_decisions_root : "#{build_decisions_root}/#{out_dir_rel}"
    FileUtils.mkdir_p(out_dir)
    depth = 1 + (out_dir_rel == '.' ? 0 : out_dir_rel.split('/').size)
    doc.root_prefix = '../' * depth
    doc.specifications_path = "./#{doc.root_prefix}specifications/"
    doc.to_html(NavigationPane.new(doc), "#{out_dir}/")
  end
end

#render_all_protocolsObject



570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
# File 'lib/almirah/project.rb', line 570

def render_all_protocols
  path = @configuration.project_root_directory

  FileUtils.mkdir_p("#{path}/build/tests/protocols")

  @project_data.protocols.each do |doc|
    img_src_dir = "#{path}/tests/protocols/#{doc.id}/img"
    img_dst_dir = "#{path}/build/tests/protocols/#{doc.id}/img"

    FileUtils.mkdir_p(img_dst_dir)

    FileUtils.copy_entry(img_src_dir, img_dst_dir) if File.directory?(img_src_dir)

    nav_pane = NavigationPane.new(doc)
    doc.to_html(nav_pane, "#{path}/build/tests/protocols/")
  end
end

#render_all_risk_recordsObject

Each risk record renders to its own page under build/risks//, with the navigation pane, exactly as decision records do (ADR-215).



684
685
686
687
688
689
690
691
692
693
694
695
696
697
# File 'lib/almirah/project.rb', line 684

def render_all_risk_records
  return if @project_data.risk_records.empty?

  build_risks_root = "#{@configuration.project_root_directory}/build/risks"
  @project_data.risk_records.each do |doc|
    out_dir_rel = File.dirname(doc.html_rel_path)
    out_dir = "#{build_risks_root}/#{out_dir_rel}"
    FileUtils.mkdir_p(out_dir)
    depth = 1 + out_dir_rel.split('/').size
    doc.root_prefix = '../' * depth
    doc.specifications_path = "./#{doc.root_prefix}specifications/"
    doc.to_html(NavigationPane.new(doc), "#{out_dir}/")
  end
end

#render_all_source_filesObject



588
589
590
591
592
593
594
595
# File 'lib/almirah/project.rb', line 588

def render_all_source_files
  path = @configuration.project_root_directory
  FileUtils.mkdir_p("#{path}/build/source_files")

  @project_data.source_files.each do |doc|
    doc.to_html("#{path}/build/source_files/")
  end
end

#render_all_specifications(spec_list) ⇒ Object



552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
# File 'lib/almirah/project.rb', line 552

def render_all_specifications(spec_list)
  path = @configuration.project_root_directory

  FileUtils.mkdir_p("#{path}/build/specifications")

  spec_list.each do |doc|
    img_src_dir = "#{path}/specifications/#{doc.id}/img"
    img_dst_dir = "#{path}/build/specifications/#{doc.id}/img"

    FileUtils.mkdir_p(img_dst_dir)

    FileUtils.copy_entry(img_src_dir, img_dst_dir) if File.directory?(img_src_dir)

    nav_pane = NavigationPane.new(doc)
    doc.to_html(nav_pane, "#{path}/build/specifications/")
  end
end

#render_critical_chain_pageObject



614
615
616
617
618
619
620
621
622
# File 'lib/almirah/project.rb', line 614

def render_critical_chain_page
  return if @project_data.decisions.empty?

  path = @configuration.project_root_directory
  FileUtils.mkdir_p("#{path}/build/decisions")

  doc = DocFabric.create_critical_chain_page(@project)
  doc.to_html("#{path}/build/decisions/")
end

#render_decisions_overviewObject



604
605
606
607
608
609
610
611
612
# File 'lib/almirah/project.rb', line 604

def render_decisions_overview
  return if @project_data.decisions.empty?

  path = @configuration.project_root_directory
  FileUtils.mkdir_p("#{path}/build/decisions")

  doc = DocFabric.create_decisions_overview(@project)
  doc.to_html("#{path}/build/decisions/")
end

#render_indexObject



597
598
599
600
601
602
# File 'lib/almirah/project.rb', line 597

def render_index
  path = @configuration.project_root_directory

  doc = @index
  doc.to_html("#{path}/build/")
end

#render_risk_registry_pagesObject

Each registry renders to build/risks//overview.html (ADR-216): the rendered preface first, then the register table of the registry's records, with the columns configured under the risks: root of project.yml (implicit columns plus Status when unconfigured). A registry holding only an overview.md still renders, with an empty table. Runs after render_all_risk_records so every record carries its rendering paths.



645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
# File 'lib/almirah/project.rb', line 645

def render_risk_registry_pages
  registry_names = risk_registry_names
  return if registry_names.empty?

  path = @configuration.project_root_directory
  registry_names.each do |name|
    records = @project_data.risk_registries.find { |g| g.key?(name) }&.fetch(name) || []
    preface = @project_data.risk_registry_prefaces[name]
    if preface
      preface.root_prefix = '../../'
      preface.specifications_path = "./#{preface.root_prefix}specifications/"
    end
    doc = DocFabric.create_risk_registry_page(name, records, preface, @configuration.get_risk_columns(name),
                                              @configuration.get_risk_rpn_groups(name))
    out_dir = "#{path}/build/risks/#{name}"
    FileUtils.mkdir_p(out_dir)
    doc.to_html("#{out_dir}/")
  end
end

#render_risks_overviewObject

The all-registries summary page (ADR-219): build/risks/overview.html, one row per registry with the total, open, and leading-group RPN aggregates. Rendered whenever the project has at least one registry — the same condition that emits the top-menu Risks button.



669
670
671
672
673
674
675
676
677
678
679
680
# File 'lib/almirah/project.rb', line 669

def render_risks_overview
  registry_names = risk_registry_names
  return if registry_names.empty?

  registries = registry_names.map do |name|
    [name, @project_data.risk_registries.find { |g| g.key?(name) }&.fetch(name) || []]
  end
  path = @configuration.project_root_directory
  FileUtils.mkdir_p("#{path}/build/risks")
  doc = DocFabric.create_risks_overview(registries, @configuration, @project_data.risk_registry_prefaces)
  doc.to_html("#{path}/build/risks/")
end

Reports cross-document links that could not be resolved (ADR-186, SRS-094), naming the linking document. The build still completes.



119
120
121
122
123
124
125
# File 'lib/almirah/project.rb', line 119

def report_broken_links
  broken = TextLine.broken_links
  return if broken.empty?

  ConsoleReporter.warn('broken links', broken.length)
  broken.each { |b| puts ConsoleReporter.warn_detail("  #{b[:document] || '?'}: #{b[:target]}") }
end

#report_duplicate_risk_idsObject

Two risk records sharing one id would collide in the project-wide link space; each registry is expected to use its own letter prefix (ADR-215). Reported as a non-failing warning, like broken links.



386
387
388
389
390
391
392
393
394
395
# File 'lib/almirah/project.rb', line 386

def report_duplicate_risk_ids
  duplicates = @project_data.risk_records.group_by(&:id).select { |_id, records| records.length > 1 }
  return if duplicates.empty?

  ConsoleReporter.warn('duplicated risk ids', duplicates.length)
  duplicates.each do |id, records|
    files = records.map { |r| r.path.sub("#{@configuration.project_root_directory}/", '') }.join(', ')
    puts ConsoleReporter.warn_detail("  #{id}: #{files}")
  end
end

#report_kit_violationsObject

Reports the two kit gates and unresolved Depends On references (ADR-194), all as non-failing console warnings alongside report_broken_links.



211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
# File 'lib/almirah/project.rb', line 211

def report_kit_violations
  @kit_unresolved ||= []
  phase = @project_data.work_items.each_value.select(&:phase_order_violation?)
  cross = @project_data.work_items.each_value.select(&:cross_record_violation?)
  total = phase.length + cross.length + @kit_unresolved.length
  return if total.zero?

  ConsoleReporter.warn('kit violations', total)
  phase.each do |wi|
    blocking = wi.intra_record_predecessors.reject(&:done?).map(&:id).join(', ')
    puts ConsoleReporter.warn_detail("  phase order: #{wi.id} started before #{blocking}")
  end
  cross.each do |wi|
    blocking = wi.cross_record_predecessors.reject(&:done?).map(&:id).join(', ')
    puts ConsoleReporter.warn_detail("  not kitted: #{wi.id} needs #{blocking}")
  end
  @kit_unresolved.each do |u|
    puts ConsoleReporter.warn_detail("  unresolved Depends On: #{u[:record]} -> #{u[:target]}")
  end
end

#report_renderedObject



111
112
113
114
115
# File 'lib/almirah/project.rb', line 111

def report_rendered
  root = @configuration.project_root_directory
  base = root == Dir.pwd ? '.' : root
  ConsoleReporter.result('rendering HTML', File.join(base, 'build', 'index.html'))
end

#risk_registry_namesObject

The registry names in file-system (parse) order: every first-level risks/ folder holding records or a preface. The set that earns the top-menu Risks button (ADR-219) and a registry page (ADR-216).



353
354
355
356
# File 'lib/almirah/project.rb', line 353

def risk_registry_names
  (@project_data.risk_registries.map { |g| g.keys.first } +
   @project_data.risk_registry_prefaces.keys).uniq
end

#specifications_and_protocolsObject



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/almirah/project.rb', line 43

def specifications_and_protocols
  parse_all_specifications
  parse_all_protocols
  parse_all_source_files
  parse_decisions
  parse_risks
  link_all_specifications
  link_all_protocols
  link_all_source_files
  link_all_decisions
  link_all_risks
  check_wrong_specification_referenced
  build_link_registry
  link_work_items
  create_index
  render_all_specifications(@project_data.specifications)
  render_all_specifications(@project_data.traceability_matrices)
  render_all_specifications(@project_data.coverage_matrices)
  render_all_protocols
  render_all_source_files
  render_all_specifications(@project_data.implementation_matrices) # intentionally after source file rendering
  render_decisions_overview
  render_critical_chain_page
  render_all_decisions
  render_all_risk_records
  render_risk_registry_pages
  render_risks_overview
  render_index
  create_search_data
  report_broken_links
  report_kit_violations
  report_rendered
end

#specifications_and_results(test_run) ⇒ Object



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/almirah/project.rb', line 77

def specifications_and_results(test_run)
  parse_all_specifications
  parse_test_run test_run
  parse_all_source_files
  parse_decisions
  parse_risks
  link_all_specifications
  link_all_protocols
  link_all_source_files
  link_all_decisions
  link_all_risks
  check_wrong_specification_referenced
  build_link_registry
  link_work_items
  create_index
  render_all_specifications(@project_data.specifications)
  render_all_specifications(@project_data.traceability_matrices)
  render_all_specifications(@project_data.coverage_matrices)
  render_all_protocols
  render_all_source_files
  render_all_specifications(@project_data.implementation_matrices) # intentionally after source file rendering
  render_decisions_overview
  render_critical_chain_page
  render_all_decisions
  render_all_risk_records
  render_risk_registry_pages
  render_risks_overview
  render_index
  create_search_data
  report_broken_links
  report_kit_violations
  report_rendered
end