@smartmemory/compose 0.1.27-beta → 0.1.29-beta

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 (69) hide show
  1. package/.claude/skills/compose/SKILL.md +5 -2
  2. package/.claude/skills/compose/templates/boundary-map.md +94 -0
  3. package/bin/compose.js +27 -0
  4. package/contracts/task-result.json +60 -0
  5. package/contracts/taskgraph-gsd.json +94 -0
  6. package/dist/assets/{App-Dj7XWWxC.js → App-DyRUFvbx.js} +67 -67
  7. package/dist/assets/{_baseUniq-tNOA7dYy.js → _baseUniq-6fxo8lI4.js} +1 -1
  8. package/dist/assets/{arc-BAmAJ19S.js → arc-ElMd2B94.js} +1 -1
  9. package/dist/assets/{architectureDiagram-Q4EWVU46-BPWGVKHW.js → architectureDiagram-Q4EWVU46-CYkxf4MU.js} +1 -1
  10. package/dist/assets/{blockDiagram-DXYQGD6D-CVlFbWKF.js → blockDiagram-DXYQGD6D-BEbnyY6z.js} +1 -1
  11. package/dist/assets/{c4Diagram-AHTNJAMY-CqzLpnSp.js → c4Diagram-AHTNJAMY-Bo5yMBT9.js} +1 -1
  12. package/dist/assets/channel-BZuHuJYj.js +1 -0
  13. package/dist/assets/{chunk-4BX2VUAB-D27n9FGy.js → chunk-4BX2VUAB-BETV09jq.js} +1 -1
  14. package/dist/assets/{chunk-4TB4RGXK-BNXf8s1x.js → chunk-4TB4RGXK-Dbuohq0Y.js} +1 -1
  15. package/dist/assets/{chunk-55IACEB6-kGd4Gwx6.js → chunk-55IACEB6-CDVuU3Ew.js} +1 -1
  16. package/dist/assets/{chunk-EDXVE4YY-Ci9gWeIv.js → chunk-EDXVE4YY-DjkbltSG.js} +1 -1
  17. package/dist/assets/{chunk-FMBD7UC4-B-C0Qn-h.js → chunk-FMBD7UC4-Dz2qczuu.js} +1 -1
  18. package/dist/assets/{chunk-OYMX7WX6-CosYsxuv.js → chunk-OYMX7WX6-XJH4cbpv.js} +1 -1
  19. package/dist/assets/{chunk-QZHKN3VN-DV2v0Qii.js → chunk-QZHKN3VN-DPhBd22Q.js} +1 -1
  20. package/dist/assets/{chunk-YZCP3GAM-BlgKQRCn.js → chunk-YZCP3GAM-C-DPngtC.js} +1 -1
  21. package/dist/assets/classDiagram-6PBFFD2Q-DTItsUL2.js +1 -0
  22. package/dist/assets/classDiagram-v2-HSJHXN6E-DTItsUL2.js +1 -0
  23. package/dist/assets/clone-BM9Hd7mq.js +1 -0
  24. package/dist/assets/{cose-bilkent-S5V4N54A-CSkzhGHO.js → cose-bilkent-S5V4N54A-CN-sdzRq.js} +1 -1
  25. package/dist/assets/{dagre-KV5264BT-zp76534d.js → dagre-KV5264BT-HcBmaeC1.js} +1 -1
  26. package/dist/assets/{diagram-5BDNPKRD-CAsORZBT.js → diagram-5BDNPKRD-DeMZN1_c.js} +1 -1
  27. package/dist/assets/{diagram-G4DWMVQ6-Da2z6fvR.js → diagram-G4DWMVQ6-DAVSapDS.js} +1 -1
  28. package/dist/assets/{diagram-MMDJMWI5-R9NZEWPF.js → diagram-MMDJMWI5-WiLMNWTz.js} +1 -1
  29. package/dist/assets/{diagram-TYMM5635-DXabRna8.js → diagram-TYMM5635-CjfaWKT0.js} +1 -1
  30. package/dist/assets/{erDiagram-SMLLAGMA-B1zsRPqn.js → erDiagram-SMLLAGMA-DUT_laNT.js} +1 -1
  31. package/dist/assets/{flowDiagram-DWJPFMVM-AvlZ6pTE.js → flowDiagram-DWJPFMVM-J9uyMWkp.js} +1 -1
  32. package/dist/assets/{ganttDiagram-T4ZO3ILL-Bnj-jTcM.js → ganttDiagram-T4ZO3ILL-Cee_YjtS.js} +1 -1
  33. package/dist/assets/{gitGraphDiagram-UUTBAWPF-82ysfuqG.js → gitGraphDiagram-UUTBAWPF-B1jNNoiW.js} +1 -1
  34. package/dist/assets/{graph-6nRhlKgL.js → graph-DV5DY72d.js} +1 -1
  35. package/dist/assets/{index-CF7jc-By.js → index-D5Mh04yh.js} +2 -2
  36. package/dist/assets/{infoDiagram-42DDH7IO-DSGEEGYr.js → infoDiagram-42DDH7IO-m1jIMAlx.js} +1 -1
  37. package/dist/assets/{ishikawaDiagram-UXIWVN3A-COnZHJuM.js → ishikawaDiagram-UXIWVN3A-BC7DwQNb.js} +1 -1
  38. package/dist/assets/{journeyDiagram-VCZTEJTY-Bsssj2jr.js → journeyDiagram-VCZTEJTY-BOCgv7m4.js} +1 -1
  39. package/dist/assets/{kanban-definition-6JOO6SKY-1o5Em0Ia.js → kanban-definition-6JOO6SKY-Fu0eFxr1.js} +1 -1
  40. package/dist/assets/{layout-C6Mitjz_.js → layout-C-R3-tDf.js} +1 -1
  41. package/dist/assets/{linear-DoGSpDWQ.js → linear-NOMW_E2I.js} +1 -1
  42. package/dist/assets/{min-BqH4I4oK.js → min-B7SuZW29.js} +1 -1
  43. package/dist/assets/{mindmap-definition-QFDTVHPH-79V6zmXV.js → mindmap-definition-QFDTVHPH-BPwx_SxA.js} +1 -1
  44. package/dist/assets/{pieDiagram-DEJITSTG-nXz4HiT6.js → pieDiagram-DEJITSTG-DdBjaXTu.js} +1 -1
  45. package/dist/assets/{quadrantDiagram-34T5L4WZ-BuwKLcii.js → quadrantDiagram-34T5L4WZ-D_K_ZMnx.js} +1 -1
  46. package/dist/assets/{requirementDiagram-MS252O5E-9YEiDjlT.js → requirementDiagram-MS252O5E-XlROD_VH.js} +1 -1
  47. package/dist/assets/{sankeyDiagram-XADWPNL6-gX8lhWn5.js → sankeyDiagram-XADWPNL6-Sl0FllYQ.js} +1 -1
  48. package/dist/assets/{sequenceDiagram-FGHM5R23-CkssrD67.js → sequenceDiagram-FGHM5R23-D22asQ-_.js} +1 -1
  49. package/dist/assets/{stateDiagram-FHFEXIEX-DPmEy2eV.js → stateDiagram-FHFEXIEX-BnCepkBg.js} +1 -1
  50. package/dist/assets/stateDiagram-v2-QKLJ7IA2-DD_YjrTQ.js +1 -0
  51. package/dist/assets/{timeline-definition-GMOUNBTQ-DGpUOjs3.js → timeline-definition-GMOUNBTQ-gdlx6TEB.js} +1 -1
  52. package/dist/assets/{vennDiagram-DHZGUBPP-CIvkd661.js → vennDiagram-DHZGUBPP-wy4YzhrG.js} +1 -1
  53. package/dist/assets/{wardley-RL74JXVD-BqRTpa3K.js → wardley-RL74JXVD-D0T_uQ79.js} +1 -1
  54. package/dist/assets/{wardleyDiagram-NUSXRM2D-B93hOd7R.js → wardleyDiagram-NUSXRM2D-Ds2dLjs8.js} +1 -1
  55. package/dist/assets/{xychartDiagram-5P7HB3ND-C6hwimqo.js → xychartDiagram-5P7HB3ND-CLgTTXkj.js} +1 -1
  56. package/dist/index.html +1 -1
  57. package/lib/boundary-map.js +481 -0
  58. package/lib/gsd-blackboard.js +135 -0
  59. package/lib/gsd-decompose-enrich.js +171 -0
  60. package/lib/gsd-prompt.js +82 -0
  61. package/lib/gsd.js +364 -0
  62. package/package.json +1 -1
  63. package/pipelines/build.stratum.yaml +7 -2
  64. package/pipelines/gsd.stratum.yaml +141 -0
  65. package/dist/assets/channel-Ddcaj0fR.js +0 -1
  66. package/dist/assets/classDiagram-6PBFFD2Q-DfGxrNIN.js +0 -1
  67. package/dist/assets/classDiagram-v2-HSJHXN6E-DfGxrNIN.js +0 -1
  68. package/dist/assets/clone-DVujR_lO.js +0 -1
  69. package/dist/assets/stateDiagram-v2-QKLJ7IA2-BGZzYkLq.js +0 -1
@@ -1,4 +1,4 @@
1
- import{s as gi,g as xi,q as Xt,p as di,a as fi,b as pi,_ as a,l as Yt,I as mi,e as yi,z as bi,D as _t,i as Ai,F as Nt,G as wi,K as Ci,aF as Si,R as Wt}from"./App-Dj7XWWxC.js";import{i as _i}from"./init-Gi6I4Gst.js";import{o as ki}from"./ordinal-Cboi1Yqb.js";import{l as zt}from"./linear-DoGSpDWQ.js";import"./mobile-BOyZ87uL.js";import"./index-CF7jc-By.js";import"./graph-CfEl_ohV.js";import"./defaultLocale-DX6XiGOO.js";function Ri(e,t,i){e=+e,t=+t,i=(n=arguments.length)<2?(t=e,e=0,1):n<3?1:+i;for(var s=-1,n=Math.max(0,Math.ceil((t-e)/i))|0,o=new Array(n);++s<n;)o[s]=e+s*i;return o}function bt(){var e=ki().unknown(void 0),t=e.domain,i=e.range,s=0,n=1,o,u,p=!1,f=0,T=0,P=.5;delete e.unknown;function _(){var y=t().length,E=n<s,v=E?n:s,L=E?s:n;o=(L-v)/Math.max(1,y-f+T*2),p&&(o=Math.floor(o)),v+=(L-v-o*(y-f))*P,u=o*(1-f),p&&(v=Math.round(v),u=Math.round(u));var I=Ri(y).map(function(m){return v+o*m});return i(E?I.reverse():I)}return e.domain=function(y){return arguments.length?(t(y),_()):t()},e.range=function(y){return arguments.length?([s,n]=y,s=+s,n=+n,_()):[s,n]},e.rangeRound=function(y){return[s,n]=y,s=+s,n=+n,p=!0,_()},e.bandwidth=function(){return u},e.step=function(){return o},e.round=function(y){return arguments.length?(p=!!y,_()):p},e.padding=function(y){return arguments.length?(f=Math.min(1,T=+y),_()):f},e.paddingInner=function(y){return arguments.length?(f=Math.min(1,y),_()):f},e.paddingOuter=function(y){return arguments.length?(T=+y,_()):T},e.align=function(y){return arguments.length?(P=Math.max(0,Math.min(1,y)),_()):P},e.copy=function(){return bt(t(),[s,n]).round(p).paddingInner(f).paddingOuter(T).align(P)},_i.apply(_(),arguments)}var At=(function(){var e=a(function(F,h,c,g){for(c=c||{},g=F.length;g--;c[F[g]]=h);return c},"o"),t=[1,10,12,14,16,18,19,21,23],i=[2,6],s=[1,3],n=[1,5],o=[1,6],u=[1,7],p=[1,5,10,12,14,16,18,19,21,23,34,35,36],f=[1,25],T=[1,26],P=[1,28],_=[1,29],y=[1,30],E=[1,31],v=[1,32],L=[1,33],I=[1,34],m=[1,35],R=[1,36],l=[1,37],W=[1,43],O=[1,42],X=[1,47],Y=[1,50],S=[1,10,12,14,16,18,19,21,23,34,35,36],U=[1,10,12,14,16,18,19,21,23,24,26,27,28,34,35,36],b=[1,10,12,14,16,18,19,21,23,24,26,27,28,34,35,36,41,42,43,44,45,46,47,48,49,50],w=[1,64],V={trace:a(function(){},"trace"),yy:{},symbols_:{error:2,start:3,eol:4,XYCHART:5,chartConfig:6,document:7,CHART_ORIENTATION:8,statement:9,title:10,text:11,X_AXIS:12,parseXAxis:13,Y_AXIS:14,parseYAxis:15,LINE:16,plotData:17,BAR:18,acc_title:19,acc_title_value:20,acc_descr:21,acc_descr_value:22,acc_descr_multiline_value:23,SQUARE_BRACES_START:24,commaSeparatedNumbers:25,SQUARE_BRACES_END:26,NUMBER_WITH_DECIMAL:27,COMMA:28,xAxisData:29,bandData:30,ARROW_DELIMITER:31,commaSeparatedTexts:32,yAxisData:33,NEWLINE:34,SEMI:35,EOF:36,alphaNum:37,STR:38,MD_STR:39,alphaNumToken:40,AMP:41,NUM:42,ALPHA:43,PLUS:44,EQUALS:45,MULT:46,DOT:47,BRKT:48,MINUS:49,UNDERSCORE:50,$accept:0,$end:1},terminals_:{2:"error",5:"XYCHART",8:"CHART_ORIENTATION",10:"title",12:"X_AXIS",14:"Y_AXIS",16:"LINE",18:"BAR",19:"acc_title",20:"acc_title_value",21:"acc_descr",22:"acc_descr_value",23:"acc_descr_multiline_value",24:"SQUARE_BRACES_START",26:"SQUARE_BRACES_END",27:"NUMBER_WITH_DECIMAL",28:"COMMA",31:"ARROW_DELIMITER",34:"NEWLINE",35:"SEMI",36:"EOF",38:"STR",39:"MD_STR",41:"AMP",42:"NUM",43:"ALPHA",44:"PLUS",45:"EQUALS",46:"MULT",47:"DOT",48:"BRKT",49:"MINUS",50:"UNDERSCORE"},productions_:[0,[3,2],[3,3],[3,2],[3,1],[6,1],[7,0],[7,2],[9,2],[9,2],[9,2],[9,2],[9,2],[9,3],[9,2],[9,3],[9,2],[9,2],[9,1],[17,3],[25,3],[25,1],[13,1],[13,2],[13,1],[29,1],[29,3],[30,3],[32,3],[32,1],[15,1],[15,2],[15,1],[33,3],[4,1],[4,1],[4,1],[11,1],[11,1],[11,1],[37,1],[37,2],[40,1],[40,1],[40,1],[40,1],[40,1],[40,1],[40,1],[40,1],[40,1],[40,1]],performAction:a(function(h,c,g,x,C,r,rt){var d=r.length-1;switch(C){case 5:x.setOrientation(r[d]);break;case 9:x.setDiagramTitle(r[d].text.trim());break;case 12:x.setLineData({text:"",type:"text"},r[d]);break;case 13:x.setLineData(r[d-1],r[d]);break;case 14:x.setBarData({text:"",type:"text"},r[d]);break;case 15:x.setBarData(r[d-1],r[d]);break;case 16:this.$=r[d].trim(),x.setAccTitle(this.$);break;case 17:case 18:this.$=r[d].trim(),x.setAccDescription(this.$);break;case 19:this.$=r[d-1];break;case 20:this.$=[Number(r[d-2]),...r[d]];break;case 21:this.$=[Number(r[d])];break;case 22:x.setXAxisTitle(r[d]);break;case 23:x.setXAxisTitle(r[d-1]);break;case 24:x.setXAxisTitle({type:"text",text:""});break;case 25:x.setXAxisBand(r[d]);break;case 26:x.setXAxisRangeData(Number(r[d-2]),Number(r[d]));break;case 27:this.$=r[d-1];break;case 28:this.$=[r[d-2],...r[d]];break;case 29:this.$=[r[d]];break;case 30:x.setYAxisTitle(r[d]);break;case 31:x.setYAxisTitle(r[d-1]);break;case 32:x.setYAxisTitle({type:"text",text:""});break;case 33:x.setYAxisRangeData(Number(r[d-2]),Number(r[d]));break;case 37:this.$={text:r[d],type:"text"};break;case 38:this.$={text:r[d],type:"text"};break;case 39:this.$={text:r[d],type:"markdown"};break;case 40:this.$=r[d];break;case 41:this.$=r[d-1]+""+r[d];break}},"anonymous"),table:[e(t,i,{3:1,4:2,7:4,5:s,34:n,35:o,36:u}),{1:[3]},e(t,i,{4:2,7:4,3:8,5:s,34:n,35:o,36:u}),e(t,i,{4:2,7:4,6:9,3:10,5:s,8:[1,11],34:n,35:o,36:u}),{1:[2,4],9:12,10:[1,13],12:[1,14],14:[1,15],16:[1,16],18:[1,17],19:[1,18],21:[1,19],23:[1,20]},e(p,[2,34]),e(p,[2,35]),e(p,[2,36]),{1:[2,1]},e(t,i,{4:2,7:4,3:21,5:s,34:n,35:o,36:u}),{1:[2,3]},e(p,[2,5]),e(t,[2,7],{4:22,34:n,35:o,36:u}),{11:23,37:24,38:f,39:T,40:27,41:P,42:_,43:y,44:E,45:v,46:L,47:I,48:m,49:R,50:l},{11:39,13:38,24:W,27:O,29:40,30:41,37:24,38:f,39:T,40:27,41:P,42:_,43:y,44:E,45:v,46:L,47:I,48:m,49:R,50:l},{11:45,15:44,27:X,33:46,37:24,38:f,39:T,40:27,41:P,42:_,43:y,44:E,45:v,46:L,47:I,48:m,49:R,50:l},{11:49,17:48,24:Y,37:24,38:f,39:T,40:27,41:P,42:_,43:y,44:E,45:v,46:L,47:I,48:m,49:R,50:l},{11:52,17:51,24:Y,37:24,38:f,39:T,40:27,41:P,42:_,43:y,44:E,45:v,46:L,47:I,48:m,49:R,50:l},{20:[1,53]},{22:[1,54]},e(S,[2,18]),{1:[2,2]},e(S,[2,8]),e(S,[2,9]),e(U,[2,37],{40:55,41:P,42:_,43:y,44:E,45:v,46:L,47:I,48:m,49:R,50:l}),e(U,[2,38]),e(U,[2,39]),e(b,[2,40]),e(b,[2,42]),e(b,[2,43]),e(b,[2,44]),e(b,[2,45]),e(b,[2,46]),e(b,[2,47]),e(b,[2,48]),e(b,[2,49]),e(b,[2,50]),e(b,[2,51]),e(S,[2,10]),e(S,[2,22],{30:41,29:56,24:W,27:O}),e(S,[2,24]),e(S,[2,25]),{31:[1,57]},{11:59,32:58,37:24,38:f,39:T,40:27,41:P,42:_,43:y,44:E,45:v,46:L,47:I,48:m,49:R,50:l},e(S,[2,11]),e(S,[2,30],{33:60,27:X}),e(S,[2,32]),{31:[1,61]},e(S,[2,12]),{17:62,24:Y},{25:63,27:w},e(S,[2,14]),{17:65,24:Y},e(S,[2,16]),e(S,[2,17]),e(b,[2,41]),e(S,[2,23]),{27:[1,66]},{26:[1,67]},{26:[2,29],28:[1,68]},e(S,[2,31]),{27:[1,69]},e(S,[2,13]),{26:[1,70]},{26:[2,21],28:[1,71]},e(S,[2,15]),e(S,[2,26]),e(S,[2,27]),{11:59,32:72,37:24,38:f,39:T,40:27,41:P,42:_,43:y,44:E,45:v,46:L,47:I,48:m,49:R,50:l},e(S,[2,33]),e(S,[2,19]),{25:73,27:w},{26:[2,28]},{26:[2,20]}],defaultActions:{8:[2,1],10:[2,3],21:[2,2],72:[2,28],73:[2,20]},parseError:a(function(h,c){if(c.recoverable)this.trace(h);else{var g=new Error(h);throw g.hash=c,g}},"parseError"),parse:a(function(h){var c=this,g=[0],x=[],C=[null],r=[],rt=this.table,d="",ct=0,It=0,hi=2,Mt=1,li=r.slice.call(arguments,1),D=Object.create(this.lexer),$={yy:{}};for(var ft in this.yy)Object.prototype.hasOwnProperty.call(this.yy,ft)&&($.yy[ft]=this.yy[ft]);D.setInput(h,$.yy),$.yy.lexer=D,$.yy.parser=this,typeof D.yylloc>"u"&&(D.yylloc={});var pt=D.yylloc;r.push(pt);var ci=D.options&&D.options.ranges;typeof $.yy.parseError=="function"?this.parseError=$.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;function ui(B){g.length=g.length-2*B,C.length=C.length-B,r.length=r.length-B}a(ui,"popStack");function Vt(){var B;return B=x.pop()||D.lex()||Mt,typeof B!="number"&&(B instanceof Array&&(x=B,B=x.pop()),B=c.symbols_[B]||B),B}a(Vt,"lex");for(var M,q,z,mt,G={},ut,N,Bt,gt;;){if(q=g[g.length-1],this.defaultActions[q]?z=this.defaultActions[q]:((M===null||typeof M>"u")&&(M=Vt()),z=rt[q]&&rt[q][M]),typeof z>"u"||!z.length||!z[0]){var yt="";gt=[];for(ut in rt[q])this.terminals_[ut]&&ut>hi&&gt.push("'"+this.terminals_[ut]+"'");D.showPosition?yt="Parse error on line "+(ct+1)+`:
1
+ import{s as gi,g as xi,q as Xt,p as di,a as fi,b as pi,_ as a,l as Yt,I as mi,e as yi,z as bi,D as _t,i as Ai,F as Nt,G as wi,K as Ci,aF as Si,R as Wt}from"./App-DyRUFvbx.js";import{i as _i}from"./init-Gi6I4Gst.js";import{o as ki}from"./ordinal-Cboi1Yqb.js";import{l as zt}from"./linear-NOMW_E2I.js";import"./mobile-BOyZ87uL.js";import"./index-D5Mh04yh.js";import"./graph-CfEl_ohV.js";import"./defaultLocale-DX6XiGOO.js";function Ri(e,t,i){e=+e,t=+t,i=(n=arguments.length)<2?(t=e,e=0,1):n<3?1:+i;for(var s=-1,n=Math.max(0,Math.ceil((t-e)/i))|0,o=new Array(n);++s<n;)o[s]=e+s*i;return o}function bt(){var e=ki().unknown(void 0),t=e.domain,i=e.range,s=0,n=1,o,u,p=!1,f=0,T=0,P=.5;delete e.unknown;function _(){var y=t().length,E=n<s,v=E?n:s,L=E?s:n;o=(L-v)/Math.max(1,y-f+T*2),p&&(o=Math.floor(o)),v+=(L-v-o*(y-f))*P,u=o*(1-f),p&&(v=Math.round(v),u=Math.round(u));var I=Ri(y).map(function(m){return v+o*m});return i(E?I.reverse():I)}return e.domain=function(y){return arguments.length?(t(y),_()):t()},e.range=function(y){return arguments.length?([s,n]=y,s=+s,n=+n,_()):[s,n]},e.rangeRound=function(y){return[s,n]=y,s=+s,n=+n,p=!0,_()},e.bandwidth=function(){return u},e.step=function(){return o},e.round=function(y){return arguments.length?(p=!!y,_()):p},e.padding=function(y){return arguments.length?(f=Math.min(1,T=+y),_()):f},e.paddingInner=function(y){return arguments.length?(f=Math.min(1,y),_()):f},e.paddingOuter=function(y){return arguments.length?(T=+y,_()):T},e.align=function(y){return arguments.length?(P=Math.max(0,Math.min(1,y)),_()):P},e.copy=function(){return bt(t(),[s,n]).round(p).paddingInner(f).paddingOuter(T).align(P)},_i.apply(_(),arguments)}var At=(function(){var e=a(function(F,h,c,g){for(c=c||{},g=F.length;g--;c[F[g]]=h);return c},"o"),t=[1,10,12,14,16,18,19,21,23],i=[2,6],s=[1,3],n=[1,5],o=[1,6],u=[1,7],p=[1,5,10,12,14,16,18,19,21,23,34,35,36],f=[1,25],T=[1,26],P=[1,28],_=[1,29],y=[1,30],E=[1,31],v=[1,32],L=[1,33],I=[1,34],m=[1,35],R=[1,36],l=[1,37],W=[1,43],O=[1,42],X=[1,47],Y=[1,50],S=[1,10,12,14,16,18,19,21,23,34,35,36],U=[1,10,12,14,16,18,19,21,23,24,26,27,28,34,35,36],b=[1,10,12,14,16,18,19,21,23,24,26,27,28,34,35,36,41,42,43,44,45,46,47,48,49,50],w=[1,64],V={trace:a(function(){},"trace"),yy:{},symbols_:{error:2,start:3,eol:4,XYCHART:5,chartConfig:6,document:7,CHART_ORIENTATION:8,statement:9,title:10,text:11,X_AXIS:12,parseXAxis:13,Y_AXIS:14,parseYAxis:15,LINE:16,plotData:17,BAR:18,acc_title:19,acc_title_value:20,acc_descr:21,acc_descr_value:22,acc_descr_multiline_value:23,SQUARE_BRACES_START:24,commaSeparatedNumbers:25,SQUARE_BRACES_END:26,NUMBER_WITH_DECIMAL:27,COMMA:28,xAxisData:29,bandData:30,ARROW_DELIMITER:31,commaSeparatedTexts:32,yAxisData:33,NEWLINE:34,SEMI:35,EOF:36,alphaNum:37,STR:38,MD_STR:39,alphaNumToken:40,AMP:41,NUM:42,ALPHA:43,PLUS:44,EQUALS:45,MULT:46,DOT:47,BRKT:48,MINUS:49,UNDERSCORE:50,$accept:0,$end:1},terminals_:{2:"error",5:"XYCHART",8:"CHART_ORIENTATION",10:"title",12:"X_AXIS",14:"Y_AXIS",16:"LINE",18:"BAR",19:"acc_title",20:"acc_title_value",21:"acc_descr",22:"acc_descr_value",23:"acc_descr_multiline_value",24:"SQUARE_BRACES_START",26:"SQUARE_BRACES_END",27:"NUMBER_WITH_DECIMAL",28:"COMMA",31:"ARROW_DELIMITER",34:"NEWLINE",35:"SEMI",36:"EOF",38:"STR",39:"MD_STR",41:"AMP",42:"NUM",43:"ALPHA",44:"PLUS",45:"EQUALS",46:"MULT",47:"DOT",48:"BRKT",49:"MINUS",50:"UNDERSCORE"},productions_:[0,[3,2],[3,3],[3,2],[3,1],[6,1],[7,0],[7,2],[9,2],[9,2],[9,2],[9,2],[9,2],[9,3],[9,2],[9,3],[9,2],[9,2],[9,1],[17,3],[25,3],[25,1],[13,1],[13,2],[13,1],[29,1],[29,3],[30,3],[32,3],[32,1],[15,1],[15,2],[15,1],[33,3],[4,1],[4,1],[4,1],[11,1],[11,1],[11,1],[37,1],[37,2],[40,1],[40,1],[40,1],[40,1],[40,1],[40,1],[40,1],[40,1],[40,1],[40,1]],performAction:a(function(h,c,g,x,C,r,rt){var d=r.length-1;switch(C){case 5:x.setOrientation(r[d]);break;case 9:x.setDiagramTitle(r[d].text.trim());break;case 12:x.setLineData({text:"",type:"text"},r[d]);break;case 13:x.setLineData(r[d-1],r[d]);break;case 14:x.setBarData({text:"",type:"text"},r[d]);break;case 15:x.setBarData(r[d-1],r[d]);break;case 16:this.$=r[d].trim(),x.setAccTitle(this.$);break;case 17:case 18:this.$=r[d].trim(),x.setAccDescription(this.$);break;case 19:this.$=r[d-1];break;case 20:this.$=[Number(r[d-2]),...r[d]];break;case 21:this.$=[Number(r[d])];break;case 22:x.setXAxisTitle(r[d]);break;case 23:x.setXAxisTitle(r[d-1]);break;case 24:x.setXAxisTitle({type:"text",text:""});break;case 25:x.setXAxisBand(r[d]);break;case 26:x.setXAxisRangeData(Number(r[d-2]),Number(r[d]));break;case 27:this.$=r[d-1];break;case 28:this.$=[r[d-2],...r[d]];break;case 29:this.$=[r[d]];break;case 30:x.setYAxisTitle(r[d]);break;case 31:x.setYAxisTitle(r[d-1]);break;case 32:x.setYAxisTitle({type:"text",text:""});break;case 33:x.setYAxisRangeData(Number(r[d-2]),Number(r[d]));break;case 37:this.$={text:r[d],type:"text"};break;case 38:this.$={text:r[d],type:"text"};break;case 39:this.$={text:r[d],type:"markdown"};break;case 40:this.$=r[d];break;case 41:this.$=r[d-1]+""+r[d];break}},"anonymous"),table:[e(t,i,{3:1,4:2,7:4,5:s,34:n,35:o,36:u}),{1:[3]},e(t,i,{4:2,7:4,3:8,5:s,34:n,35:o,36:u}),e(t,i,{4:2,7:4,6:9,3:10,5:s,8:[1,11],34:n,35:o,36:u}),{1:[2,4],9:12,10:[1,13],12:[1,14],14:[1,15],16:[1,16],18:[1,17],19:[1,18],21:[1,19],23:[1,20]},e(p,[2,34]),e(p,[2,35]),e(p,[2,36]),{1:[2,1]},e(t,i,{4:2,7:4,3:21,5:s,34:n,35:o,36:u}),{1:[2,3]},e(p,[2,5]),e(t,[2,7],{4:22,34:n,35:o,36:u}),{11:23,37:24,38:f,39:T,40:27,41:P,42:_,43:y,44:E,45:v,46:L,47:I,48:m,49:R,50:l},{11:39,13:38,24:W,27:O,29:40,30:41,37:24,38:f,39:T,40:27,41:P,42:_,43:y,44:E,45:v,46:L,47:I,48:m,49:R,50:l},{11:45,15:44,27:X,33:46,37:24,38:f,39:T,40:27,41:P,42:_,43:y,44:E,45:v,46:L,47:I,48:m,49:R,50:l},{11:49,17:48,24:Y,37:24,38:f,39:T,40:27,41:P,42:_,43:y,44:E,45:v,46:L,47:I,48:m,49:R,50:l},{11:52,17:51,24:Y,37:24,38:f,39:T,40:27,41:P,42:_,43:y,44:E,45:v,46:L,47:I,48:m,49:R,50:l},{20:[1,53]},{22:[1,54]},e(S,[2,18]),{1:[2,2]},e(S,[2,8]),e(S,[2,9]),e(U,[2,37],{40:55,41:P,42:_,43:y,44:E,45:v,46:L,47:I,48:m,49:R,50:l}),e(U,[2,38]),e(U,[2,39]),e(b,[2,40]),e(b,[2,42]),e(b,[2,43]),e(b,[2,44]),e(b,[2,45]),e(b,[2,46]),e(b,[2,47]),e(b,[2,48]),e(b,[2,49]),e(b,[2,50]),e(b,[2,51]),e(S,[2,10]),e(S,[2,22],{30:41,29:56,24:W,27:O}),e(S,[2,24]),e(S,[2,25]),{31:[1,57]},{11:59,32:58,37:24,38:f,39:T,40:27,41:P,42:_,43:y,44:E,45:v,46:L,47:I,48:m,49:R,50:l},e(S,[2,11]),e(S,[2,30],{33:60,27:X}),e(S,[2,32]),{31:[1,61]},e(S,[2,12]),{17:62,24:Y},{25:63,27:w},e(S,[2,14]),{17:65,24:Y},e(S,[2,16]),e(S,[2,17]),e(b,[2,41]),e(S,[2,23]),{27:[1,66]},{26:[1,67]},{26:[2,29],28:[1,68]},e(S,[2,31]),{27:[1,69]},e(S,[2,13]),{26:[1,70]},{26:[2,21],28:[1,71]},e(S,[2,15]),e(S,[2,26]),e(S,[2,27]),{11:59,32:72,37:24,38:f,39:T,40:27,41:P,42:_,43:y,44:E,45:v,46:L,47:I,48:m,49:R,50:l},e(S,[2,33]),e(S,[2,19]),{25:73,27:w},{26:[2,28]},{26:[2,20]}],defaultActions:{8:[2,1],10:[2,3],21:[2,2],72:[2,28],73:[2,20]},parseError:a(function(h,c){if(c.recoverable)this.trace(h);else{var g=new Error(h);throw g.hash=c,g}},"parseError"),parse:a(function(h){var c=this,g=[0],x=[],C=[null],r=[],rt=this.table,d="",ct=0,It=0,hi=2,Mt=1,li=r.slice.call(arguments,1),D=Object.create(this.lexer),$={yy:{}};for(var ft in this.yy)Object.prototype.hasOwnProperty.call(this.yy,ft)&&($.yy[ft]=this.yy[ft]);D.setInput(h,$.yy),$.yy.lexer=D,$.yy.parser=this,typeof D.yylloc>"u"&&(D.yylloc={});var pt=D.yylloc;r.push(pt);var ci=D.options&&D.options.ranges;typeof $.yy.parseError=="function"?this.parseError=$.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;function ui(B){g.length=g.length-2*B,C.length=C.length-B,r.length=r.length-B}a(ui,"popStack");function Vt(){var B;return B=x.pop()||D.lex()||Mt,typeof B!="number"&&(B instanceof Array&&(x=B,B=x.pop()),B=c.symbols_[B]||B),B}a(Vt,"lex");for(var M,q,z,mt,G={},ut,N,Bt,gt;;){if(q=g[g.length-1],this.defaultActions[q]?z=this.defaultActions[q]:((M===null||typeof M>"u")&&(M=Vt()),z=rt[q]&&rt[q][M]),typeof z>"u"||!z.length||!z[0]){var yt="";gt=[];for(ut in rt[q])this.terminals_[ut]&&ut>hi&&gt.push("'"+this.terminals_[ut]+"'");D.showPosition?yt="Parse error on line "+(ct+1)+`:
2
2
  `+D.showPosition()+`
3
3
  Expecting `+gt.join(", ")+", got '"+(this.terminals_[M]||M)+"'":yt="Parse error on line "+(ct+1)+": Unexpected "+(M==Mt?"end of input":"'"+(this.terminals_[M]||M)+"'"),this.parseError(yt,{text:D.match,token:this.terminals_[M]||M,line:D.yylineno,loc:pt,expected:gt})}if(z[0]instanceof Array&&z.length>1)throw new Error("Parse Error: multiple actions possible at state: "+q+", token: "+M);switch(z[0]){case 1:g.push(M),C.push(D.yytext),r.push(D.yylloc),g.push(z[1]),M=null,It=D.yyleng,d=D.yytext,ct=D.yylineno,pt=D.yylloc;break;case 2:if(N=this.productions_[z[1]][1],G.$=C[C.length-N],G._$={first_line:r[r.length-(N||1)].first_line,last_line:r[r.length-1].last_line,first_column:r[r.length-(N||1)].first_column,last_column:r[r.length-1].last_column},ci&&(G._$.range=[r[r.length-(N||1)].range[0],r[r.length-1].range[1]]),mt=this.performAction.apply(G,[d,It,ct,$.yy,z[1],C,r].concat(li)),typeof mt<"u")return mt;N&&(g=g.slice(0,-1*N*2),C=C.slice(0,-1*N),r=r.slice(0,-1*N)),g.push(this.productions_[z[1]][0]),C.push(G.$),r.push(G._$),Bt=rt[g[g.length-2]][g[g.length-1]],g.push(Bt);break;case 3:return!0}}return!0},"parse")},k=(function(){var F={EOF:1,parseError:a(function(c,g){if(this.yy.parser)this.yy.parser.parseError(c,g);else throw new Error(c)},"parseError"),setInput:a(function(h,c){return this.yy=c||this.yy||{},this._input=h,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},"setInput"),input:a(function(){var h=this._input[0];this.yytext+=h,this.yyleng++,this.offset++,this.match+=h,this.matched+=h;var c=h.match(/(?:\r\n?|\n).*/g);return c?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),h},"input"),unput:a(function(h){var c=h.length,g=h.split(/(?:\r\n?|\n)/g);this._input=h+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-c),this.offset-=c;var x=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),g.length-1&&(this.yylineno-=g.length-1);var C=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:g?(g.length===x.length?this.yylloc.first_column:0)+x[x.length-g.length].length-g[0].length:this.yylloc.first_column-c},this.options.ranges&&(this.yylloc.range=[C[0],C[0]+this.yyleng-c]),this.yyleng=this.yytext.length,this},"unput"),more:a(function(){return this._more=!0,this},"more"),reject:a(function(){if(this.options.backtrack_lexer)this._backtrack=!0;else return this.parseError("Lexical error on line "+(this.yylineno+1)+`. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).
4
4
  `+this.showPosition(),{text:"",token:null,line:this.yylineno});return this},"reject"),less:a(function(h){this.unput(this.match.slice(h))},"less"),pastInput:a(function(){var h=this.matched.substr(0,this.matched.length-this.match.length);return(h.length>20?"...":"")+h.substr(-20).replace(/\n/g,"")},"pastInput"),upcomingInput:a(function(){var h=this.match;return h.length<20&&(h+=this._input.substr(0,20-h.length)),(h.substr(0,20)+(h.length>20?"...":"")).replace(/\n/g,"")},"upcomingInput"),showPosition:a(function(){var h=this.pastInput(),c=new Array(h.length+1).join("-");return h+this.upcomingInput()+`
package/dist/index.html CHANGED
@@ -19,7 +19,7 @@
19
19
  }
20
20
  })();
21
21
  </script>
22
- <script type="module" crossorigin src="/assets/index-CF7jc-By.js"></script>
22
+ <script type="module" crossorigin src="/assets/index-D5Mh04yh.js"></script>
23
23
  <link rel="modulepreload" crossorigin href="/assets/mobile-BOyZ87uL.js">
24
24
  <link rel="stylesheet" crossorigin href="/assets/mobile-5PV1E6OC.css">
25
25
  <link rel="stylesheet" crossorigin href="/assets/index-CHkeTiSt.css">
@@ -0,0 +1,481 @@
1
+ // lib/boundary-map.js
2
+ //
3
+ // parseBoundaryMap(blueprintText) -> { slices: Slice[], parseViolations: Violation[] }
4
+ // validateBoundaryMap({ blueprintText, blueprintPath, repoRoot }) ->
5
+ // { ok: bool, violations: Violation[], warnings: Warning[] }
6
+ //
7
+ // See docs/features/COMP-GSD-1/{design.md, blueprint.md}.
8
+
9
+ import { readFileSync, existsSync } from 'node:fs';
10
+ import { join } from 'node:path';
11
+
12
+ const KIND_ALLOWLIST = new Set([
13
+ 'interface',
14
+ 'type',
15
+ 'function',
16
+ 'class',
17
+ 'const',
18
+ 'hook',
19
+ 'component',
20
+ ]);
21
+
22
+ const WRITE_ACTIONS = new Set([
23
+ 'new',
24
+ 'create',
25
+ 'add',
26
+ 'edit',
27
+ 'modify',
28
+ 'update',
29
+ 'refactor',
30
+ 'replace',
31
+ ]);
32
+
33
+ const FILE_PLAN_ALIASES = ['## File Plan', '## Files', '## File-by-File Plan'];
34
+
35
+ const PRODUCES_RE =
36
+ /^\s+(\S+)\s*(?:→|->)\s*([^()]+?)\s*\((interface|type|function|class|const|hook|component)\)\s*$/;
37
+ const PRODUCES_NO_KIND_RE = /^\s+(\S+)\s*(?:→|->)\s*([^()\s][^()]*?)\s*$/;
38
+ const CONSUMES_RE =
39
+ /^\s+from\s+(S\d{2,})\s*:\s*(\S+)\s*(?:→|->)\s*([^()]+?)(?:\s*\([^)]*\))?\s*$/;
40
+
41
+ export function parseBoundaryMap(blueprintText) {
42
+ const lines = blueprintText.split(/\r?\n/);
43
+ // Locate ## Boundary Map
44
+ let start = -1;
45
+ for (let i = 0; i < lines.length; i++) {
46
+ if (/^## Boundary Map\s*$/.test(lines[i])) {
47
+ start = i + 1;
48
+ break;
49
+ }
50
+ }
51
+ if (start === -1) return { slices: [], parseViolations: [] };
52
+
53
+ // End at next ## heading or EOF
54
+ let end = lines.length;
55
+ for (let i = start; i < lines.length; i++) {
56
+ if (/^## /.test(lines[i])) {
57
+ end = i;
58
+ break;
59
+ }
60
+ }
61
+
62
+ const slices = [];
63
+ const parseViolations = [];
64
+ const seenIds = new Map(); // id -> first slice index
65
+
66
+ let cur = null;
67
+ let block = null; // 'produces' | 'consumes' | null
68
+ let nothingSentinel = null; // 'produces' | 'consumes' | null — set after a `nothing` literal until cleared
69
+
70
+ function pushViolation(v) {
71
+ parseViolations.push(v);
72
+ }
73
+
74
+ for (let i = start; i < end; i++) {
75
+ const line = lines[i];
76
+ const lineNo = i + 1;
77
+
78
+ // Slice heading
79
+ const sliceM = line.match(/^### (S\d{2,})(?::\s*(.*))?\s*$/);
80
+ if (sliceM) {
81
+ const id = sliceM[1];
82
+ const name = sliceM[2] || undefined;
83
+ if (seenIds.has(id)) {
84
+ pushViolation({
85
+ kind: 'duplicate_slice_id',
86
+ scope: 'parse',
87
+ slice: id,
88
+ message: `Duplicate slice id ${id} at line ${lineNo}; first occurrence wins`,
89
+ });
90
+ // Still create a "shadow" slice so we keep parsing for further error reporting,
91
+ // but mark it as duplicate so downstream checks can ignore it.
92
+ cur = { id, name, produces: [], consumes: [], leaf: false, sink: false, line: lineNo, _duplicate: true };
93
+ slices.push(cur);
94
+ } else {
95
+ cur = { id, name, produces: [], consumes: [], leaf: false, sink: false, line: lineNo };
96
+ slices.push(cur);
97
+ seenIds.set(id, slices.length - 1);
98
+ }
99
+ block = null;
100
+ nothingSentinel = null;
101
+ continue;
102
+ }
103
+
104
+ // Block headers
105
+ const prodHdr = line.match(/^Produces:\s*(nothing\b.*)?\s*$/);
106
+ if (prodHdr && cur) {
107
+ block = 'produces';
108
+ nothingSentinel = null;
109
+ if (prodHdr[1]) {
110
+ cur.sink = true;
111
+ block = null; // no entries expected
112
+ nothingSentinel = 'produces';
113
+ }
114
+ continue;
115
+ }
116
+ const consHdr = line.match(/^Consumes:\s*(nothing\b.*)?\s*$/);
117
+ if (consHdr && cur) {
118
+ block = 'consumes';
119
+ nothingSentinel = null;
120
+ if (consHdr[1]) {
121
+ cur.leaf = true;
122
+ block = null;
123
+ nothingSentinel = 'consumes';
124
+ }
125
+ continue;
126
+ }
127
+
128
+ // Blank line clears nothing; entries are indented under the block
129
+ if (/^\s*$/.test(line)) continue;
130
+
131
+ // Entries
132
+ if (block === 'produces' && cur) {
133
+ const m = line.match(PRODUCES_RE);
134
+ if (m) {
135
+ const [, file, symbolStr, kind] = m;
136
+ const symbols = symbolStr.split(',').map((s) => s.trim()).filter(Boolean);
137
+ if (symbols.length === 0) {
138
+ pushViolation({
139
+ kind: 'malformed_entry',
140
+ scope: 'parse',
141
+ slice: cur.id,
142
+ message: `Empty symbol list at line ${lineNo}`,
143
+ });
144
+ continue;
145
+ }
146
+ cur.produces.push({ file, symbols, kind, line: lineNo });
147
+ continue;
148
+ }
149
+ // Try no-kind regex
150
+ if (PRODUCES_NO_KIND_RE.test(line)) {
151
+ pushViolation({
152
+ kind: 'missing_kind',
153
+ scope: 'parse',
154
+ slice: cur.id,
155
+ message: `Produces entry at line ${lineNo} missing required (<kind>) parenthetical`,
156
+ });
157
+ continue;
158
+ }
159
+ pushViolation({
160
+ kind: 'malformed_produces',
161
+ scope: 'parse',
162
+ slice: cur.id,
163
+ message: `Malformed Produces entry at line ${lineNo}: ${line}`,
164
+ });
165
+ continue;
166
+ }
167
+
168
+ if (block === 'consumes' && cur) {
169
+ const m = line.match(CONSUMES_RE);
170
+ if (m) {
171
+ const [, from, file, symbolStr] = m;
172
+ const symbols = symbolStr.split(',').map((s) => s.trim()).filter(Boolean);
173
+ if (symbols.length === 0) {
174
+ pushViolation({
175
+ kind: 'malformed_entry',
176
+ scope: 'parse',
177
+ slice: cur.id,
178
+ message: `Empty consume symbol list at line ${lineNo}`,
179
+ });
180
+ continue;
181
+ }
182
+ cur.consumes.push({ from, file, symbols, line: lineNo });
183
+ continue;
184
+ }
185
+ pushViolation({
186
+ kind: 'malformed_consumes',
187
+ scope: 'parse',
188
+ slice: cur.id,
189
+ message: `Malformed Consumes entry at line ${lineNo}: ${line}`,
190
+ });
191
+ continue;
192
+ }
193
+
194
+ // Non-blank line outside any block.
195
+ // After a `nothing` sentinel, an indented entry-shaped line is a parse error
196
+ // (the only valid zero-entry forms are the sentinels themselves).
197
+ if (nothingSentinel && cur && /^\s+\S/.test(line)) {
198
+ const looksLikeEntry =
199
+ PRODUCES_RE.test(line) ||
200
+ PRODUCES_NO_KIND_RE.test(line) ||
201
+ CONSUMES_RE.test(line) ||
202
+ /^\s+from\s+S\d{2,}\s*:/.test(line) ||
203
+ /(?:→|->)/.test(line);
204
+ if (looksLikeEntry) {
205
+ pushViolation({
206
+ kind: 'malformed_after_nothing',
207
+ scope: 'parse',
208
+ slice: cur.id,
209
+ message: `Slice ${cur.id} has an entry-shaped line at line ${lineNo} after a "${nothingSentinel}: nothing" sentinel; "nothing" forbids further entries`,
210
+ });
211
+ }
212
+ }
213
+ // Otherwise ignore (could be prose between slices).
214
+ }
215
+
216
+ return { slices, parseViolations };
217
+ }
218
+
219
+ // ---------- File Plan parsing ----------
220
+
221
+ function parseFilePlan(blueprintText) {
222
+ const lines = blueprintText.split(/\r?\n/);
223
+ let aliasIdx = -1;
224
+ let aliasUsed = null;
225
+ for (let i = 0; i < lines.length; i++) {
226
+ for (const alias of FILE_PLAN_ALIASES) {
227
+ if (lines[i].trim() === alias) {
228
+ aliasIdx = i;
229
+ aliasUsed = alias;
230
+ break;
231
+ }
232
+ }
233
+ if (aliasIdx !== -1) break;
234
+ }
235
+ if (aliasIdx === -1) return { found: false, entries: [] };
236
+
237
+ // Walk until next ## heading or EOF
238
+ let end = lines.length;
239
+ for (let i = aliasIdx + 1; i < lines.length; i++) {
240
+ if (/^## /.test(lines[i])) {
241
+ end = i;
242
+ break;
243
+ }
244
+ }
245
+
246
+ const entries = [];
247
+ for (let i = aliasIdx + 1; i < end; i++) {
248
+ const line = lines[i];
249
+ // Markdown table row: starts with | and has at least 2 pipes; skip header & sep
250
+ if (!/^\s*\|/.test(line)) continue;
251
+ const cells = line.split('|').slice(1, -1).map((c) => c.trim());
252
+ if (cells.length < 2) continue;
253
+ // Skip header row "File | Action | ..."
254
+ if (/^file$/i.test(cells[0]) && /^action$/i.test(cells[1])) continue;
255
+ // Skip separator row "---|---|..."
256
+ if (cells.every((c) => /^:?-+:?$/.test(c))) continue;
257
+ const fileRaw = cells[0];
258
+ const actionRaw = cells[1];
259
+ const file = fileRaw.replace(/`/g, '').trim();
260
+ if (!file) continue;
261
+ entries.push({ file, action: actionRaw, line: i + 1 });
262
+ }
263
+ return { found: true, alias: aliasUsed, entries };
264
+ }
265
+
266
+ function normalizeAction(actionRaw) {
267
+ const trimmed = actionRaw.trim();
268
+ if (!trimmed) return '';
269
+ const firstTok = trimmed.split(/\s+/)[0];
270
+ return firstTok.toLowerCase().replace(/[.,;:]+$/, '');
271
+ }
272
+
273
+ // ---------- Validator ----------
274
+
275
+ export function validateBoundaryMap({ blueprintText, blueprintPath, repoRoot }) {
276
+ const violations = [];
277
+ const warnings = [];
278
+
279
+ const { slices, parseViolations } = parseBoundaryMap(blueprintText);
280
+ for (const v of parseViolations) violations.push(v);
281
+
282
+ if (slices.length === 0 && parseViolations.length === 0) {
283
+ return { ok: true, violations: [], warnings: [] };
284
+ }
285
+
286
+ // Filter out duplicate-shadow slices for downstream checks
287
+ const liveSlices = slices.filter((s) => !s._duplicate);
288
+
289
+ // Build File Plan index — a file may appear in multiple rows; isPlannedWrite
290
+ // is true if ANY row has an allow-listed write action.
291
+ const filePlan = parseFilePlan(blueprintText);
292
+ const filePlanIndex = new Map(); // file -> { rows: [{action, normalized}], isPlannedWrite: bool }
293
+ const unknownActionWarned = new Set();
294
+
295
+ if (!filePlan.found) {
296
+ warnings.push({
297
+ kind: 'no_file_plan',
298
+ scope: 'blueprint',
299
+ message: 'Blueprint has no recognized File Plan heading',
300
+ });
301
+ } else {
302
+ for (let rowIdx = 0; rowIdx < filePlan.entries.length; rowIdx++) {
303
+ const row = filePlan.entries[rowIdx];
304
+ const norm = normalizeAction(row.action);
305
+ const isWrite = WRITE_ACTIONS.has(norm);
306
+ let entry = filePlanIndex.get(row.file);
307
+ if (!entry) {
308
+ entry = { rows: [], isPlannedWrite: false };
309
+ filePlanIndex.set(row.file, entry);
310
+ }
311
+ entry.rows.push({ action: row.action, normalized: norm });
312
+ if (isWrite) entry.isPlannedWrite = true;
313
+ const dedupKey = `${rowIdx}${row.file}${norm}`;
314
+ if (!isWrite && !unknownActionWarned.has(dedupKey)) {
315
+ unknownActionWarned.add(dedupKey);
316
+ warnings.push({
317
+ kind: 'unknown_action',
318
+ scope: 'file-plan',
319
+ file: row.file,
320
+ message: `File Plan action "${row.action}" for ${row.file} is not in the recognized write-action allow-list`,
321
+ });
322
+ }
323
+ }
324
+ }
325
+
326
+ function isPlannedWrite(file) {
327
+ const e = filePlanIndex.get(file);
328
+ return !!(e && e.isPlannedWrite);
329
+ }
330
+
331
+ function fileExists(file) {
332
+ if (!repoRoot) return false;
333
+ try {
334
+ return existsSync(join(repoRoot, file));
335
+ } catch {
336
+ return false;
337
+ }
338
+ }
339
+
340
+ // 1. File-Plan-or-disk check — emit one violation per (slice, file, symbol)
341
+ for (const slice of liveSlices) {
342
+ const entries = [
343
+ ...slice.produces.map((p) => ({ file: p.file, symbols: p.symbols })),
344
+ ...slice.consumes.map((c) => ({ file: c.file, symbols: c.symbols })),
345
+ ];
346
+ for (const entry of entries) {
347
+ if (isPlannedWrite(entry.file)) continue;
348
+ if (fileExists(entry.file)) continue;
349
+ for (const sym of entry.symbols) {
350
+ violations.push({
351
+ kind: 'missing_file',
352
+ scope: 'entry',
353
+ slice: slice.id,
354
+ file: entry.file,
355
+ symbol: sym,
356
+ message: `File ${entry.file} referenced in slice ${slice.id} (symbol ${sym}) is not in File Plan and does not exist on disk`,
357
+ });
358
+ }
359
+ }
360
+ }
361
+
362
+ // 2. Symbol presence check
363
+ for (const slice of liveSlices) {
364
+ const entries = [
365
+ ...slice.produces.map((p) => ({ file: p.file, symbols: p.symbols })),
366
+ ...slice.consumes.map((c) => ({ file: c.file, symbols: c.symbols })),
367
+ ];
368
+ for (const entry of entries) {
369
+ if (isPlannedWrite(entry.file)) continue;
370
+ if (!fileExists(entry.file)) continue; // covered by missing_file or no repoRoot
371
+ let content;
372
+ try {
373
+ content = readFileSync(join(repoRoot, entry.file), 'utf8');
374
+ } catch {
375
+ continue;
376
+ }
377
+ for (const sym of entry.symbols) {
378
+ if (!content.includes(sym)) {
379
+ violations.push({
380
+ kind: 'missing_symbol',
381
+ scope: 'entry',
382
+ slice: slice.id,
383
+ file: entry.file,
384
+ symbol: sym,
385
+ message: `Symbol ${sym} not found in ${entry.file} (slice ${slice.id})`,
386
+ });
387
+ }
388
+ }
389
+ }
390
+ }
391
+
392
+ // 3. Topology check
393
+ const sliceOrder = new Map();
394
+ liveSlices.forEach((s, i) => sliceOrder.set(s.id, i));
395
+ for (let i = 0; i < liveSlices.length; i++) {
396
+ const slice = liveSlices[i];
397
+ for (const c of slice.consumes) {
398
+ if (!sliceOrder.has(c.from)) {
399
+ for (const sym of c.symbols) {
400
+ violations.push({
401
+ kind: 'dangling_consume',
402
+ scope: 'entry',
403
+ slice: slice.id,
404
+ file: c.file,
405
+ symbol: sym,
406
+ message: `Slice ${slice.id} consumes ${sym} from ${c.from}:${c.file}, but ${c.from} has no heading in this map`,
407
+ });
408
+ }
409
+ continue;
410
+ }
411
+ const targetIdx = sliceOrder.get(c.from);
412
+ if (targetIdx >= i) {
413
+ for (const sym of c.symbols) {
414
+ violations.push({
415
+ kind: 'forward_reference',
416
+ scope: 'entry',
417
+ slice: slice.id,
418
+ file: c.file,
419
+ symbol: sym,
420
+ message: `Slice ${slice.id} consumes ${sym} from ${c.from}:${c.file}, which appears at or after ${slice.id} in document order`,
421
+ });
422
+ }
423
+ }
424
+ }
425
+ }
426
+
427
+ // 4. Producer/consumer match
428
+ for (const slice of liveSlices) {
429
+ for (const c of slice.consumes) {
430
+ const producerIdx = sliceOrder.get(c.from);
431
+ if (producerIdx === undefined) continue; // dangling already flagged
432
+ const producer = liveSlices[producerIdx];
433
+ // Find matching produces entries for the file
434
+ const producesForFile = producer.produces.filter((p) => p.file === c.file);
435
+ if (producesForFile.length === 0) {
436
+ for (const sym of c.symbols) {
437
+ violations.push({
438
+ kind: 'producer_consumer_mismatch',
439
+ scope: 'entry',
440
+ slice: slice.id,
441
+ file: c.file,
442
+ symbol: sym,
443
+ message: `Slice ${slice.id} consumes ${sym} from ${c.from}:${c.file}, but ${c.from} does not produce that file`,
444
+ });
445
+ }
446
+ continue;
447
+ }
448
+ const producedSymbols = new Set();
449
+ for (const p of producesForFile) for (const s of p.symbols) producedSymbols.add(s);
450
+ for (const sym of c.symbols) {
451
+ if (!producedSymbols.has(sym)) {
452
+ violations.push({
453
+ kind: 'producer_consumer_mismatch',
454
+ scope: 'entry',
455
+ slice: slice.id,
456
+ file: c.file,
457
+ symbol: sym,
458
+ message: `Slice ${slice.id} consumes ${sym} from ${c.from}:${c.file}, but ${c.from} does not produce ${sym} in that file`,
459
+ });
460
+ }
461
+ }
462
+ }
463
+ }
464
+
465
+ return { ok: violations.length === 0, violations, warnings };
466
+ }
467
+
468
+ // ---------- CLI for manual dogfood ----------
469
+
470
+ if (import.meta.url === `file://${process.argv[1]}`) {
471
+ const path = process.argv[2];
472
+ if (!path) {
473
+ console.error('Usage: node lib/boundary-map.js <blueprint-path> [repoRoot]');
474
+ process.exit(2);
475
+ }
476
+ const repoRoot = process.argv[3] || process.cwd();
477
+ const blueprintText = readFileSync(path, 'utf8');
478
+ const r = validateBoundaryMap({ blueprintText, blueprintPath: path, repoRoot });
479
+ console.log(JSON.stringify(r, null, 2));
480
+ process.exit(r.ok ? 0 : 1);
481
+ }
@@ -0,0 +1,135 @@
1
+ // lib/gsd-blackboard.js
2
+ //
3
+ // COMP-GSD-2 T2: blackboard I/O for GSD post-execution capture.
4
+ //
5
+ // Exports:
6
+ // read(code, opts?) → Record<taskId, TaskResult> (returns {} if absent)
7
+ // writeAll(code, taskResults, opts?) → Promise<void> (atomic batch write)
8
+ // validate(taskResult) → { ok: boolean, errors: string[] }
9
+ //
10
+ // Lock pattern: mirrors lib/completion-writer.js:48-67 (mkdir-advisory-lock).
11
+ // Atomic write: temp-file + rename (mirrors lib/journal-writer.js).
12
+ // Schema validation: contracts/task-result.json compiled lazily under ajv.
13
+
14
+ import { mkdirSync, readFileSync, writeFileSync, renameSync, rmSync, statSync, existsSync } from 'node:fs';
15
+ import { join, dirname, resolve } from 'node:path';
16
+ import { fileURLToPath } from 'node:url';
17
+
18
+ const __dirname = dirname(fileURLToPath(import.meta.url));
19
+ const PACKAGE_ROOT = resolve(__dirname, '..');
20
+
21
+ const LOCK_TIMEOUT_MS = 5000;
22
+ const LOCK_RETRY_MS = 25;
23
+
24
+ // ---------- Path helpers ----------
25
+
26
+ function blackboardPath(cwd, code) {
27
+ return join(cwd, '.compose', 'gsd', code, 'blackboard.json');
28
+ }
29
+
30
+ function lockPath(cwd, code) {
31
+ return join(cwd, '.compose', 'data', 'locks', `gsd-${code}.lock`);
32
+ }
33
+
34
+ // ---------- Lock helpers (mirrors completion-writer.js:56) ----------
35
+
36
+ async function acquireLock(cwd, code) {
37
+ const lp = lockPath(cwd, code);
38
+ mkdirSync(dirname(lp), { recursive: true });
39
+
40
+ const start = Date.now();
41
+ while (true) {
42
+ try {
43
+ mkdirSync(lp);
44
+ return () => {
45
+ try { rmSync(lp, { recursive: true, force: true }); } catch { /* best-effort */ }
46
+ };
47
+ } catch (err) {
48
+ if (err.code !== 'EEXIST') throw err;
49
+ try {
50
+ const st = statSync(lp);
51
+ if (Date.now() - st.mtimeMs > LOCK_TIMEOUT_MS) {
52
+ rmSync(lp, { recursive: true, force: true });
53
+ continue;
54
+ }
55
+ } catch { /* stat raced; retry */ }
56
+ if (Date.now() - start > LOCK_TIMEOUT_MS) {
57
+ throw new Error(`gsd-blackboard: lock timeout after ${LOCK_TIMEOUT_MS}ms: ${lp}`);
58
+ }
59
+ await new Promise((r) => setTimeout(r, LOCK_RETRY_MS));
60
+ }
61
+ }
62
+ }
63
+
64
+ // ---------- Schema validation (lazy ajv) ----------
65
+
66
+ let _validator = null;
67
+
68
+ function getValidator() {
69
+ if (_validator) return _validator;
70
+ const Ajv = require('ajv');
71
+ const schema = JSON.parse(
72
+ readFileSync(join(PACKAGE_ROOT, 'contracts', 'task-result.json'), 'utf-8'),
73
+ );
74
+ const ajv = new Ajv({ strict: false, allErrors: true });
75
+ _validator = ajv.compile(schema);
76
+ return _validator;
77
+ }
78
+
79
+ // CommonJS require shim for ajv (this module is ESM)
80
+ import { createRequire } from 'node:module';
81
+ const require = createRequire(import.meta.url);
82
+
83
+ export function validate(taskResult) {
84
+ if (taskResult === null || typeof taskResult !== 'object' || Array.isArray(taskResult)) {
85
+ return { ok: false, errors: ['expected TaskResult object'] };
86
+ }
87
+ const v = getValidator();
88
+ const ok = v(taskResult);
89
+ if (ok) return { ok: true, errors: [] };
90
+ return {
91
+ ok: false,
92
+ errors: (v.errors ?? []).map((e) => `${e.instancePath || '/'} ${e.message}`),
93
+ };
94
+ }
95
+
96
+ // ---------- Public I/O ----------
97
+
98
+ export function read(code, opts = {}) {
99
+ const cwd = opts.cwd ?? process.cwd();
100
+ const path = blackboardPath(cwd, code);
101
+ if (!existsSync(path)) return {};
102
+ return JSON.parse(readFileSync(path, 'utf-8'));
103
+ }
104
+
105
+ export async function writeAll(code, taskResults, opts = {}) {
106
+ const cwd = opts.cwd ?? process.cwd();
107
+
108
+ // Validate every entry BEFORE acquiring the lock — fail fast, don't hold the lock for nothing.
109
+ if (taskResults === null || typeof taskResults !== 'object' || Array.isArray(taskResults)) {
110
+ throw new Error('gsd-blackboard.writeAll: invalid taskResults — expected object map');
111
+ }
112
+ for (const [taskId, result] of Object.entries(taskResults)) {
113
+ const v = validate(result);
114
+ if (!v.ok) {
115
+ throw new Error(
116
+ `gsd-blackboard.writeAll: invalid TaskResult for task "${taskId}": ${v.errors.join('; ')}`,
117
+ );
118
+ }
119
+ }
120
+
121
+ const release = await acquireLock(cwd, code);
122
+ try {
123
+ const path = blackboardPath(cwd, code);
124
+ mkdirSync(dirname(path), { recursive: true });
125
+ // One-shot batch finalization: replace whatever was there. The blackboard
126
+ // is a post-execution artifact, not an append log. Concurrent writers
127
+ // serialize on the lock — one wins, one waits — and the last writer's
128
+ // map is the final state.
129
+ const tmpPath = `${path}.tmp.${process.pid}.${Date.now()}`;
130
+ writeFileSync(tmpPath, JSON.stringify(taskResults, null, 2) + '\n');
131
+ renameSync(tmpPath, path);
132
+ } finally {
133
+ release();
134
+ }
135
+ }