forma-arch 0.11.0 → 0.11.2

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/README.md CHANGED
@@ -9,16 +9,19 @@ Forma is the companion to `arbiter`: arbiter governs the process, Forma shows th
9
9
  guarantees the picture matches reality.
10
10
 
11
11
  **[▶ Live demo](https://LucaDominici.github.io/forma/)** — **not** this repo. It is `haben`, a
12
- private 53-package Go application, generated by forma at haben commit `b1bb318`: six domains over
13
- 53 packages, 192 edges read from real `import` blocks, and box text quoted from that repo's own
14
- feature matrix. A tool proving itself on its own 28 nodes proves nothing you care about.
12
+ private 53-package Go application, generated by forma at haben commit `4c9b6880`: six domains over
13
+ 53 packages, 193 edges read from real `import` blocks, and box text quoted from that repo's own
14
+ feature matrix. Its five-checkpoint architecture timeline is governed by haben's current product
15
+ boundary: the 63-node AS-IS baseline grows cumulatively to 66 nodes and 196 edges, while the final
16
+ operational checkpoint honestly reports no architecture change. A tool proving itself on its own
17
+ 28 nodes proves nothing you care about.
15
18
 
16
19
  What you are looking at is checked, not asserted: at that commit `node scripts/presentable.mjs`
17
20
  exits 0 on the model — every level under 24 boxes, every box carrying prose rather than a file
18
21
  count, every level with more than one box drawing at least one arrow — and `forma check` exits 0
19
22
  against haben's source at the same commit. The curation that turns 53 flat packages into six
20
- domains is one human decision, checked in at [`docs/demo/curate.mjs`](docs/demo/curate.mjs) with
21
- the commands to rebuild it.
23
+ domains and maps the governed boundary to typed timeline patches is one human decision, checked in
24
+ at [`docs/demo/curate.mjs`](docs/demo/curate.mjs) with the commands to rebuild it.
22
25
 
23
26
  The one thing the demo does not do is regenerate itself: haben is private, so Pages publishes a
24
27
  snapshot committed from a local run rather than cloning it in CI. Nothing automated will notice
@@ -88,7 +91,7 @@ and validates every cumulative state before replacing the last model.
88
91
  "timeline": {
89
92
  "source": "docs/architecture/FUTURE.md",
90
93
  "checkpoints": [{
91
- "id": "g1", "label": "G1 · UAT", "badge": "9 item · 1 P0",
94
+ "id": "g1", "label": "G1 · UAT", "badge": "9 board · 1 P0",
92
95
  "patch": {
93
96
  "nodes": {
94
97
  "add": [{ "node": {
@@ -112,14 +115,17 @@ and validates every cumulative state before replacing the last model.
112
115
  ```
113
116
 
114
117
  Node patches support `add`, non-structural `update`, and child-first `remove`; edge patches support
115
- `add`, exact-match `rewire`, and `remove`. Every operation carries the prose shown as “Change from
116
- previous”. IDs, parents, levels and kinds cannot be edited in place, ambiguous edge selectors fail,
117
- and a node cannot disappear while children or incident relations survive. The source path must
118
- exist. `AS-IS` is implicit and reserved; the last checkpoint is the target, so `target` is forbidden
119
- inside timeline patches. With a timeline the viewer replaces CURRENT/TARGET with the checkpoints,
120
- supports `?checkpoint=g1`, accents only the local patch, and keeps drill, layout, re-verification
121
- and export working on the cumulative graph. Without one, the legacy controls and model are
122
- unchanged.
118
+ `add`, exact-match `rewire`, and `remove`. Every operation carries governed change prose; a live
119
+ node changed locally exposes it as “Change from previous”, while the graph accents changed nodes
120
+ and relations without keeping a second change log or drawing ghosts for removals. IDs, parents,
121
+ levels and kinds cannot be edited in place, ambiguous edge selectors fail, and a node cannot
122
+ disappear while children or incident relations survive. The source path must exist. `AS-IS` is
123
+ implicit and reserved; the last checkpoint is the target, so `target` is forbidden inside timeline
124
+ patches. With a timeline the viewer replaces CURRENT/TARGET with the checkpoints, supports
125
+ `?checkpoint=g1`, accents only the local patch, and keeps drill, layout, re-verification and export
126
+ working on the cumulative graph. A future checkpoint with no local patch says “no architecture
127
+ changes”; its display-only badge is not interpreted. Without a timeline, the legacy controls and
128
+ model are unchanged.
123
129
 
124
130
  **Curated state, verified against reality.** `forma verify` asks your `gh` CLI for the state of every issue the model references (`--gh-repo owner/repo`, or `meta.ghRepo` in the topology), marks the nodes whose issues are closed as done, and prefixes their `current` with dated evidence. It touches state, never structure, and re-running it never stacks the evidence. It is opt-in and separate on purpose: `gen` and `check` never open a socket. In the served viewer, **RE-VERIFY** re-reads the model without losing your level, layout or mode.
125
131
 
@@ -41,9 +41,6 @@ select#skin option{background:#08202e;color:var(--nm)}
41
41
  .cpstep.active .dot{border-color:var(--cyan);background:var(--bg);box-shadow:0 0 10px rgba(62,231,255,.8)}
42
42
  #checkpoint-summary{text-align:right;color:var(--ter2);font-size:8.5px;letter-spacing:.08em;margin-top:5px;min-height:11px}
43
43
  #checkpoint-summary b{color:var(--cyan);font-weight:600}
44
- #checkpoint-changes{display:grid;gap:3px;margin-top:7px;max-height:92px;overflow:auto}
45
- .cpchange{color:var(--text);font-size:9px;line-height:1.35;padding-left:8px;border-left:2px solid var(--cyan)}
46
- .cpchange b{color:var(--cyan);font-weight:600;letter-spacing:.08em}.cpchange span{color:var(--ter2)}
47
44
  #crumb{font-size:11px;color:var(--sec);margin:8px 0}
48
45
  #crumb .agg{float:right;color:var(--ter2);letter-spacing:.05em}
49
46
  #crumb .agg i{display:inline-block;width:7px;height:7px;border-radius:50%;margin:0 3px 0 8px;vertical-align:baseline}
@@ -190,9 +187,8 @@ html[data-skin="blueprint"] .nd.delta rect,html[data-skin="blueprint"] .edge.del
190
187
  </div>
191
188
  </div>
192
189
  <nav id="timeline">
193
- <div id="timeline-track"></div>
194
- <div id="checkpoint-summary"></div>
195
- <div id="checkpoint-changes"></div>
190
+ <div id="timeline-track"></div>
191
+ <div id="checkpoint-summary"></div>
196
192
  </nav>
197
193
  <div id="crumb"></div>
198
194
  <div id="stamp"></div>
@@ -229,7 +225,7 @@ var STRINGS={
229
225
  detDecl:"Declared in", detCover:"Coverage", detRoll:"Rolled up from", rollPart:"mean of the {n} of {t} parts inside that anyone ruled on", coverWhole:"the document names this module itself",
230
226
  coverPart:"{n} of {t} units named by the document",
231
227
  legHint:"— animated arrow = active flow · dashed = to build", legCheckpoint:"CHANGE IN THIS CHECKPOINT",
232
- timelineLabel:"Architecture checkpoints", asIs:"AS-IS", asIsBadge:"verified code", cumulativeHint:"the graph is always cumulative",
228
+ timelineLabel:"Architecture checkpoints", asIs:"AS-IS", asIsBadge:"verified code", cumulativeHint:"the graph is always cumulative", noArchChanges:"no architecture changes",
233
229
  deltaAdd:"ADD", deltaUpdate:"UPDATE", deltaRewire:"REWIRE", deltaRemove:"REMOVE",
234
230
  crumbRoot:"Context", crumbLevel:"C4", lvlContext:"CONTEXT",
235
231
  lvlNames:{context:"Context",container:"Container",component:"Components",leaf:"Leaves"},
@@ -253,7 +249,7 @@ var STRINGS={
253
249
  detDecl:"Dichiarato in", detCover:"Copertura", detRoll:"Somma dal basso", rollPart:"media delle {n} parti su {t} qui dentro su cui qualcuno si e pronunciato", coverWhole:"il documento nomina il modulo stesso",
254
250
  coverPart:"{n} unità su {t} nominate dal documento",
255
251
  legHint:"— freccia animata = flusso attivo · tratteggio = da costruire", legCheckpoint:"CAMBIO IN QUESTO CHECKPOINT",
256
- timelineLabel:"Checkpoint architetturali", asIs:"AS-IS", asIsBadge:"codice verificato", cumulativeHint:"il grafo è sempre cumulativo",
252
+ timelineLabel:"Checkpoint architetturali", asIs:"AS-IS", asIsBadge:"codice verificato", cumulativeHint:"il grafo è sempre cumulativo", noArchChanges:"nessun cambio architetturale",
257
253
  deltaAdd:"ADD", deltaUpdate:"UPDATE", deltaRewire:"REWIRE", deltaRemove:"REMOVE",
258
254
  crumbRoot:"Contesto", crumbLevel:"C4", lvlContext:"CONTESTO",
259
255
  lvlNames:{context:"Contesto",container:"Container",component:"Componenti",leaf:"Foglie"},
@@ -396,6 +392,14 @@ function timelineStates(base){
396
392
  }
397
393
  return {errors:errors,states:states};
398
394
  }
395
+ function timelineSummary(id,delta){
396
+ var c=(delta&&delta.counts)||{},parts=[];
397
+ if(c.add)parts.push({key:"deltaAdd",count:c.add});
398
+ if(c.update)parts.push({key:"deltaUpdate",count:c.update});
399
+ if(c.rewire)parts.push({key:"deltaRewire",count:c.rewire});
400
+ if(c.remove)parts.push({key:"deltaRemove",count:c.remove});
401
+ return {parts:parts,empty:id!=="as-is"&&!parts.length};
402
+ }
399
403
  var BASE=null,M=null,TSTATES=[],CPID="as-is",CPDELTA={nodes:[],edges:[],removedNodes:[],removedEdges:[],counts:{add:0,update:0,rewire:0,remove:0}}
400
404
  var stack=[null],mode="now",POS={},POSALL={},drag=null,justDragged=false,CATNODES={},FETCHED=false
401
405
  var VBOX={}; // last fitted viewBox per level — frozen while dragging so the canvas does not rescale
@@ -462,7 +466,6 @@ function makeCatalogNode(pid,g){
462
466
  }
463
467
  function hasKids(id){for(var i=0;i<M.nodes.length;i++)if(M.nodes[i].parent===id)return true;return false;}
464
468
  function node(id){for(var i=0;i<M.nodes.length;i++)if(M.nodes[i].id===id)return M.nodes[i];return null;}
465
- function nameOf(id){var n=node(id);return n?n.name:id;}
466
469
  function srcOf(n){if(CPID!=="as-is"&&nodeProjected(n.id)&&BASE&&BASE.timeline)return BASE.timeline.source;
467
470
  if(n.verify){if(typeof n.verify==="string")return n.verify;if(n.verify.source)return n.verify.source;if(n.verify.ref)return n.verify.ref;if(n.verify.issue)return "#"+n.verify.issue;}
468
471
  var ev=(n.evidence||[]).filter(function(e){return e.type==="doc"||e.type==="path";})[0];
@@ -475,25 +478,16 @@ function edgeChange(e){
475
478
  function emptyDelta(){return {nodes:[],edges:[],removedNodes:[],removedEdges:[],counts:{add:0,update:0,rewire:0,remove:0}};}
476
479
  function renderTimeline(){
477
480
  var nav=$("timeline"),legacy=$("legacytime"),legend=$("lg-checkpoint-wrap");
478
- if(!BASE||!BASE.timeline){nav.style.display="none";legacy.style.display="inline-flex";legend.style.display="none";$("checkpoint-changes").innerHTML="";return;}
481
+ if(!BASE||!BASE.timeline){nav.style.display="none";legacy.style.display="inline-flex";legend.style.display="none";return;}
479
482
  nav.style.display="block";legacy.style.display="none";legend.style.display="inline";
480
483
  nav.setAttribute("aria-label",STR.timelineLabel);
481
484
  var html='<button class="cpstep real'+(CPID==="as-is"?" active":"")+'" data-checkpoint="as-is" type="button"><span class="dot"></span>'+esc(STR.asIs)+'<span class="cpbadge">'+esc(STR.asIsBadge)+'</span></button>';
482
485
  for(var i=0;i<TSTATES.length;i++){var s=TSTATES[i];html+='<button class="cpstep'+(CPID===s.id?" active":"")+'" data-checkpoint="'+esc(s.id)+'" type="button"><span class="dot"></span>'+esc(s.label)+(s.badge?'<span class="cpbadge">'+esc(s.badge)+'</span>':'')+'</button>';}
483
486
  $("timeline-track").innerHTML=html;
484
- var c=CPDELTA.counts,parts=[];
485
- if(c.add)parts.push('<b>'+STR.deltaAdd+'</b> '+c.add);
486
- if(c.update)parts.push('<b>'+STR.deltaUpdate+'</b> '+c.update);
487
- if(c.rewire)parts.push('<b>'+STR.deltaRewire+'</b> '+c.rewire);
488
- if(c.remove)parts.push('<b>'+STR.deltaRemove+'</b> '+c.remove);
489
- $("checkpoint-summary").innerHTML=(parts.length?parts.join(" · ")+" · ":"")+esc(STR.cumulativeHint);
490
- var changes=[],d=CPDELTA,i,nm,subject;
491
- function row(type,what,why){changes.push('<div class="cpchange"><b>'+esc(type)+'</b> '+esc(what)+' <span>— '+esc(why)+'</span></div>');}
492
- for(i=0;i<d.nodes.length;i++){nm=node(d.nodes[i].id);row(d.nodes[i].type,nm?nm.name:d.nodes[i].id,d.nodes[i].change);}
493
- for(i=0;i<d.edges.length;i++){subject=nameOf(d.edges[i].from)+" → "+nameOf(d.edges[i].to)+(d.edges[i].label?" · "+d.edges[i].label:"");row(d.edges[i].type,subject,d.edges[i].change);}
494
- for(i=0;i<d.removedEdges.length;i++){subject=nameOf(d.removedEdges[i].from)+" → "+nameOf(d.removedEdges[i].to)+(d.removedEdges[i].label?" · "+d.removedEdges[i].label:"");row(d.removedEdges[i].type,subject,d.removedEdges[i].change);}
495
- for(i=0;i<d.removedNodes.length;i++)row(d.removedNodes[i].type,d.removedNodes[i].name||d.removedNodes[i].id,d.removedNodes[i].change);
496
- $("checkpoint-changes").innerHTML=changes.join("");
487
+ var summary=timelineSummary(CPID,CPDELTA),parts=[];
488
+ for(i=0;i<summary.parts.length;i++)parts.push('<b>'+STR[summary.parts[i].key]+'</b> '+summary.parts[i].count);
489
+ $("checkpoint-summary").innerHTML=parts.length?parts.join(" · ")+" · "+esc(STR.cumulativeHint)
490
+ :(summary.empty?'<b>'+esc(STR.noArchChanges)+'</b> · ':'')+esc(STR.cumulativeHint);
497
491
  }
498
492
  function checkpointUrl(id){
499
493
  try{var u=new URL(location.href);if(id==="as-is")u.searchParams.delete("checkpoint");else u.searchParams.set("checkpoint",id);history.replaceState(null,"",u.toString());}catch(e){}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forma-arch",
3
- "version": "0.11.0",
3
+ "version": "0.11.2",
4
4
  "description": "Forma — present your architecture instead of slides. An interactive, stack-agnostic C4 explorer (context → container → component → leaf) generated from your code and kept true to it by a deterministic drift check.",
5
5
  "keywords": [
6
6
  "architecture",