@smartmemory/compose 0.1.33-beta → 0.1.35-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.
- package/README.md +42 -0
- package/bin/compose.js +22 -4
- package/dist/assets/{App-1c_7rycT.js → App-DMCO9aNs.js} +5 -5
- package/dist/assets/{arc-5LGcKaXC.js → arc-DsXb95RZ.js} +1 -1
- package/dist/assets/{architectureDiagram-3BPJPVTR-BGsvZBSe.js → architectureDiagram-3BPJPVTR-BaBYippI.js} +1 -1
- package/dist/assets/{blockDiagram-GPEHLZMM-CppawQ7y.js → blockDiagram-GPEHLZMM-HwB_eL3_.js} +1 -1
- package/dist/assets/{c4Diagram-AAUBKEIU-u6006T49.js → c4Diagram-AAUBKEIU-CPSXghc8.js} +1 -1
- package/dist/assets/channel-TOlxWxU-.js +1 -0
- package/dist/assets/{chunk-2J33WTMH-VIEW3rXo.js → chunk-2J33WTMH-CCN3bc9J.js} +1 -1
- package/dist/assets/{chunk-4BX2VUAB-NhdSQDlD.js → chunk-4BX2VUAB-DuqFxpoV.js} +1 -1
- package/dist/assets/{chunk-55IACEB6-BmobEJTk.js → chunk-55IACEB6-DT20mkDV.js} +1 -1
- package/dist/assets/{chunk-727SXJPM-CkHPoJZX.js → chunk-727SXJPM-ByMH6Qvp.js} +1 -1
- package/dist/assets/{chunk-AQP2D5EJ-DhlFWYJI.js → chunk-AQP2D5EJ-CLgYtOHw.js} +1 -1
- package/dist/assets/{chunk-FMBD7UC4-Dzli17sA.js → chunk-FMBD7UC4-BXWmTsAA.js} +1 -1
- package/dist/assets/{chunk-ND2GUHAM-BZM0pKix.js → chunk-ND2GUHAM-C2WgVbpE.js} +1 -1
- package/dist/assets/{chunk-QZHKN3VN-Bw_VU3w3.js → chunk-QZHKN3VN-DFuQRJeh.js} +1 -1
- package/dist/assets/classDiagram-4FO5ZUOK-DZsvwI1V.js +1 -0
- package/dist/assets/classDiagram-v2-Q7XG4LA2-DZsvwI1V.js +1 -0
- package/dist/assets/{cose-bilkent-S5V4N54A-CUhMB816.js → cose-bilkent-S5V4N54A-CdyvK5N2.js} +1 -1
- package/dist/assets/{dagre-BM42HDAG-DaUl17l5.js → dagre-BM42HDAG-Drkta_n5.js} +1 -1
- package/dist/assets/{diagram-2AECGRRQ-CPZwMXfB.js → diagram-2AECGRRQ-BRiBkuu5.js} +1 -1
- package/dist/assets/{diagram-5GNKFQAL-B9-N5E5R.js → diagram-5GNKFQAL-IrSBDK26.js} +1 -1
- package/dist/assets/{diagram-KO2AKTUF-CkfEzzGz.js → diagram-KO2AKTUF-BUktYepH.js} +1 -1
- package/dist/assets/{diagram-LMA3HP47-DLAWiWIJ.js → diagram-LMA3HP47-B5erGOiF.js} +1 -1
- package/dist/assets/{diagram-OG6HWLK6-DTCE5jRG.js → diagram-OG6HWLK6-5KoSfwod.js} +1 -1
- package/dist/assets/{erDiagram-TEJ5UH35-BCIuV-ft.js → erDiagram-TEJ5UH35-CXSf-i6t.js} +1 -1
- package/dist/assets/{flowDiagram-I6XJVG4X-DmztP2OP.js → flowDiagram-I6XJVG4X-DiwEgd9q.js} +1 -1
- package/dist/assets/{ganttDiagram-6RSMTGT7-CXZwtSzf.js → ganttDiagram-6RSMTGT7-zQ94YEl2.js} +1 -1
- package/dist/assets/{gitGraphDiagram-PVQCEYII-XDMhXGQ7.js → gitGraphDiagram-PVQCEYII-CWNWantF.js} +1 -1
- package/dist/assets/{index-CCu-56GD.js → index-CyFM4bTc.js} +2 -2
- package/dist/assets/{infoDiagram-5YYISTIA-Da1XvUtC.js → infoDiagram-5YYISTIA-BcnrgEm6.js} +1 -1
- package/dist/assets/{ishikawaDiagram-YF4QCWOH-DTw3_1vI.js → ishikawaDiagram-YF4QCWOH-BRzURsJQ.js} +1 -1
- package/dist/assets/{journeyDiagram-JHISSGLW-1q57kJBE.js → journeyDiagram-JHISSGLW-CdwMwMPo.js} +1 -1
- package/dist/assets/{kanban-definition-UN3LZRKU-CHEt8drj.js → kanban-definition-UN3LZRKU-Difj4Zd-.js} +1 -1
- package/dist/assets/{katex-CQk2-UhE.js → katex-C5jXJg4s.js} +3 -3
- package/dist/assets/{linear-Dm9uGrY9.js → linear-CKwgBFBW.js} +1 -1
- package/dist/assets/{mindmap-definition-RKZ34NQL-AfCfq9He.js → mindmap-definition-RKZ34NQL-C2aCD1L4.js} +1 -1
- package/dist/assets/{pieDiagram-4H26LBE5-BwZQV6vN.js → pieDiagram-4H26LBE5-C9qGrfV0.js} +1 -1
- package/dist/assets/{quadrantDiagram-W4KKPZXB-BSH6R7JT.js → quadrantDiagram-W4KKPZXB-COA0Z2JV.js} +1 -1
- package/dist/assets/{requirementDiagram-4Y6WPE33-DgXRRUBB.js → requirementDiagram-4Y6WPE33-BJCU8yFE.js} +1 -1
- package/dist/assets/{sankeyDiagram-5OEKKPKP-zwgrKh_8.js → sankeyDiagram-5OEKKPKP-vYzK7FJ6.js} +1 -1
- package/dist/assets/{sequenceDiagram-3UESZ5HK-CCbQVXMX.js → sequenceDiagram-3UESZ5HK-Bkh_RWpN.js} +1 -1
- package/dist/assets/{stateDiagram-AJRCARHV-BIBvILDx.js → stateDiagram-AJRCARHV-BlUsbYTW.js} +1 -1
- package/dist/assets/stateDiagram-v2-BHNVJYJU-_AUWPuja.js +1 -0
- package/dist/assets/{timeline-definition-PNZ67QCA-tTCXjToz.js → timeline-definition-PNZ67QCA-DuYEZwxg.js} +1 -1
- package/dist/assets/{vennDiagram-CIIHVFJN-Bxb5N93O.js → vennDiagram-CIIHVFJN-D9a3Q3Ni.js} +1 -1
- package/dist/assets/{wardley-L42UT6IY-Dw-_rF2D.js → wardley-L42UT6IY-h2fQnc_J.js} +1 -1
- package/dist/assets/{wardleyDiagram-YWT4CUSO-rMkJu1Qs.js → wardleyDiagram-YWT4CUSO-C-_dzSY5.js} +1 -1
- package/dist/assets/{xychartDiagram-2RQKCTM6-B_CW8fFS.js → xychartDiagram-2RQKCTM6-CKxMIB7j.js} +1 -1
- package/dist/index.html +1 -1
- package/lib/build.js +60 -13
- package/lib/changelog-writer.js +111 -83
- package/lib/completion-writer.js +26 -9
- package/lib/feature-writer.js +62 -38
- package/lib/roadmap-gen.js +41 -14
- package/lib/tracker/cli.js +31 -0
- package/lib/tracker/factory.js +93 -0
- package/lib/tracker/github-api.js +115 -0
- package/lib/tracker/github-provider.js +641 -0
- package/lib/tracker/local-provider.js +202 -0
- package/lib/tracker/provider.js +40 -0
- package/lib/tracker/sync-engine.js +131 -0
- package/package.json +3 -2
- package/dist/assets/channel-9SpKIWte.js +0 -1
- package/dist/assets/classDiagram-4FO5ZUOK-1x8GC0fS.js +0 -1
- package/dist/assets/classDiagram-v2-Q7XG4LA2-1x8GC0fS.js +0 -1
- package/dist/assets/stateDiagram-v2-BHNVJYJU-CpvhwvDI.js +0 -1
package/dist/assets/{xychartDiagram-2RQKCTM6-B_CW8fFS.js → xychartDiagram-2RQKCTM6-CKxMIB7j.js}
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{b0 as gi,a2 as xi,a6 as Xt,b3 as di,a3 as fi,b1 as pi,b as a,aE as Yt,a$ as mi,F as yi,t as bi,a4 as _t,aZ as Ai,s as Nt,T as wi,af as Ci,B as Si,aB as Wt}from"./App-
|
|
1
|
+
import{b0 as gi,a2 as xi,a6 as Xt,b3 as di,a3 as fi,b1 as pi,b as a,aE as Yt,a$ as mi,F as yi,t as bi,a4 as _t,aZ as Ai,s as Nt,T as wi,af as Ci,B as Si,aB as Wt}from"./App-DMCO9aNs.js";import{i as _i}from"./init-Gi6I4Gst.js";import{o as ki}from"./ordinal-Cboi1Yqb.js";import{l as zt}from"./linear-CKwgBFBW.js";import"./mobile-CsuriFuT.js";import"./index-CyFM4bTc.js";import"./graph-DPbJeZyN.js";import"./defaultLocale-CrowFXzY.js";function Ti(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,R=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+R*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 M=Ti(y).map(function(m){return v+o*m});return i(E?M.reverse():M)}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,R=+y),_()):f},e.paddingInner=function(y){return arguments.length?(f=Math.min(1,y),_()):f},e.paddingOuter=function(y){return arguments.length?(R=+y,_()):R},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(R).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],R=[1,26],P=[1,28],_=[1,29],y=[1,30],E=[1,31],v=[1,32],L=[1,33],M=[1,34],m=[1,35],T=[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:R,40:27,41:P,42:_,43:y,44:E,45:v,46:L,47:M,48:m,49:T,50:l},{11:39,13:38,24:W,27:O,29:40,30:41,37:24,38:f,39:R,40:27,41:P,42:_,43:y,44:E,45:v,46:L,47:M,48:m,49:T,50:l},{11:45,15:44,27:X,33:46,37:24,38:f,39:R,40:27,41:P,42:_,43:y,44:E,45:v,46:L,47:M,48:m,49:T,50:l},{11:49,17:48,24:Y,37:24,38:f,39:R,40:27,41:P,42:_,43:y,44:E,45:v,46:L,47:M,48:m,49:T,50:l},{11:52,17:51,24:Y,37:24,38:f,39:R,40:27,41:P,42:_,43:y,44:E,45:v,46:L,47:M,48:m,49:T,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:M,48:m,49:T,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:R,40:27,41:P,42:_,43:y,44:E,45:v,46:L,47:M,48:m,49:T,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:R,40:27,41:P,42:_,43:y,44:E,45:v,46:L,47:M,48:m,49:T,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,Mt=0,hi=2,It=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()||It,typeof B!="number"&&(B instanceof Array&&(x=B,B=x.pop()),B=c.symbols_[B]||B),B}a(Vt,"lex");for(var I,q,z,mt,G={},ut,N,Bt,gt;;){if(q=g[g.length-1],this.defaultActions[q]?z=this.defaultActions[q]:((I===null||typeof I>"u")&&(I=Vt()),z=rt[q]&&rt[q][I]),typeof z>"u"||!z.length||!z[0]){var yt="";gt=[];for(ut in rt[q])this.terminals_[ut]&&ut>hi&>.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_[I]||I)+"'":yt="Parse error on line "+(ct+1)+": Unexpected "+(I==It?"end of input":"'"+(this.terminals_[I]||I)+"'"),this.parseError(yt,{text:D.match,token:this.terminals_[I]||I,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: "+I);switch(z[0]){case 1:g.push(I),C.push(D.yytext),r.push(D.yylloc),g.push(z[1]),I=null,Mt=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,Mt,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-
|
|
22
|
+
<script type="module" crossorigin src="/assets/index-CyFM4bTc.js"></script>
|
|
23
23
|
<link rel="modulepreload" crossorigin href="/assets/mobile-CsuriFuT.js">
|
|
24
24
|
<link rel="stylesheet" crossorigin href="/assets/mobile-5PV1E6OC.css">
|
|
25
25
|
<link rel="stylesheet" crossorigin href="/assets/index-CHkeTiSt.css">
|
package/lib/build.js
CHANGED
|
@@ -31,8 +31,16 @@ import { emitSections as emitPlanSections, appendTrailers as appendSectionTraile
|
|
|
31
31
|
import { SECTIONS_DIR } from './constants.js';
|
|
32
32
|
|
|
33
33
|
import YAML from 'yaml';
|
|
34
|
-
|
|
34
|
+
// feature-json direct imports removed — mutations now go through TrackerProvider (T9)
|
|
35
35
|
import { loadFeaturesDir } from './project-paths.js';
|
|
36
|
+
|
|
37
|
+
// Lazy provider accessor — avoids circular import risk (factory → local-provider
|
|
38
|
+
// does NOT import build.js, so a static import is safe, but lazy is used for
|
|
39
|
+
// consistency with the pattern established in T7/T8 and to avoid any future risk).
|
|
40
|
+
async function getBuildProvider(cwd) {
|
|
41
|
+
const { providerFor } = await import('./tracker/factory.js');
|
|
42
|
+
return providerFor(cwd);
|
|
43
|
+
}
|
|
36
44
|
import { evaluatePolicy } from '../server/policy-evaluator.js';
|
|
37
45
|
import { runTriage, isTriageStale } from './triage.js';
|
|
38
46
|
import { shouldRunCrossModel, LENS_DEFINITIONS } from './review-lenses.js';
|
|
@@ -622,18 +630,22 @@ export async function runBuild(featureCode, opts = {}) {
|
|
|
622
630
|
// - opts.template is explicitly set (user chose a specific template)
|
|
623
631
|
// ---------------------------------------------------------------------------
|
|
624
632
|
let buildProfile = null;
|
|
633
|
+
let _buildTierLabel = '?'; // for skip_reason label in spec YAML mutation below
|
|
625
634
|
// Bug mode skips pre-build triage entirely — triage is feature-shaped
|
|
626
635
|
// (writes feature.json, profile selection per feature complexity tiers).
|
|
627
636
|
if (!isBugMode && !opts.skipTriage && !opts.template) {
|
|
628
|
-
|
|
637
|
+
const _buildProvider = await getBuildProvider(cwd);
|
|
638
|
+
let cachedFeature = await _buildProvider.getFeature(featureCode);
|
|
629
639
|
if (cachedFeature?.profile && !isTriageStale(cwd, featureCode, featuresDir)) {
|
|
630
640
|
// Reuse cached profile
|
|
631
641
|
buildProfile = cachedFeature.profile;
|
|
632
|
-
|
|
642
|
+
_buildTierLabel = cachedFeature.complexity ?? '?';
|
|
643
|
+
console.log(`[triage] Using cached profile (tier ${_buildTierLabel}): ${JSON.stringify(buildProfile)}`);
|
|
633
644
|
} else {
|
|
634
645
|
// Run fresh triage
|
|
635
646
|
const triageResult = await runTriage(featureCode, { cwd, featuresDir });
|
|
636
647
|
buildProfile = triageResult.profile;
|
|
648
|
+
_buildTierLabel = String(triageResult.tier);
|
|
637
649
|
console.log(`[triage] Tier ${triageResult.tier}: ${triageResult.rationale}`);
|
|
638
650
|
console.log(`[triage] Profile: ${JSON.stringify(buildProfile)}`);
|
|
639
651
|
|
|
@@ -641,20 +653,23 @@ export async function runBuild(featureCode, opts = {}) {
|
|
|
641
653
|
if (!cachedFeature) {
|
|
642
654
|
// Create feature.json — feature folder exists but json was missing
|
|
643
655
|
const featureDesc = opts.description ?? featureCode;
|
|
644
|
-
|
|
656
|
+
cachedFeature = await _buildProvider.createFeature(featureCode, {
|
|
645
657
|
code: featureCode,
|
|
646
658
|
description: featureDesc,
|
|
647
659
|
status: 'PLANNED',
|
|
648
660
|
complexity: String(triageResult.tier),
|
|
649
661
|
profile: buildProfile,
|
|
650
662
|
triageTimestamp,
|
|
651
|
-
}
|
|
663
|
+
});
|
|
652
664
|
} else {
|
|
653
|
-
|
|
665
|
+
// Profile/complexity cache update — no status change. Spread current
|
|
666
|
+
// feature so putFeature receives the full object (it overwrites, not merges).
|
|
667
|
+
cachedFeature = await _buildProvider.putFeature(featureCode, {
|
|
668
|
+
...cachedFeature,
|
|
654
669
|
complexity: String(triageResult.tier),
|
|
655
670
|
profile: buildProfile,
|
|
656
671
|
triageTimestamp,
|
|
657
|
-
}
|
|
672
|
+
});
|
|
658
673
|
}
|
|
659
674
|
}
|
|
660
675
|
}
|
|
@@ -689,9 +704,8 @@ export async function runBuild(featureCode, opts = {}) {
|
|
|
689
704
|
delete step.skip_reason;
|
|
690
705
|
} else if (buildProfile[needsKey] === false) {
|
|
691
706
|
// Disable step — mark as unconditionally skipped
|
|
692
|
-
const tier = readFeature(cwd, featureCode, featuresDir)?.complexity ?? '?';
|
|
693
707
|
step.skip_if = 'true';
|
|
694
|
-
step.skip_reason = `Skipped by triage (tier ${
|
|
708
|
+
step.skip_reason = `Skipped by triage (tier ${_buildTierLabel})`;
|
|
695
709
|
}
|
|
696
710
|
}
|
|
697
711
|
specYaml = YAML.stringify(specObj);
|
|
@@ -752,7 +766,16 @@ export async function runBuild(featureCode, opts = {}) {
|
|
|
752
766
|
// Update feature.json status to IN_PROGRESS (feature mode only;
|
|
753
767
|
// bug mode does not use feature.json).
|
|
754
768
|
if (!isBugMode) {
|
|
755
|
-
|
|
769
|
+
const _bp = await getBuildProvider(cwd);
|
|
770
|
+
// Guard: feature.json may not exist if triage was skipped AND no prior
|
|
771
|
+
// createFeature ran (e.g. test harnesses that only create the folder).
|
|
772
|
+
// Original updateFeature silently no-oped when feature was missing.
|
|
773
|
+
// Use persistFeatureRaw (not setStatus) — raw write with no transition policy,
|
|
774
|
+
// no events, no renderRoadmap. Matches original updateFeature semantics exactly.
|
|
775
|
+
const _feat = await _bp.getFeature(featureCode);
|
|
776
|
+
if (_feat) {
|
|
777
|
+
await _bp.persistFeatureRaw(featureCode, { ..._feat, status: 'IN_PROGRESS' });
|
|
778
|
+
}
|
|
756
779
|
}
|
|
757
780
|
|
|
758
781
|
// Hoisted for finally-block visibility
|
|
@@ -1831,7 +1854,15 @@ export async function runBuild(featureCode, opts = {}) {
|
|
|
1831
1854
|
// COMP-QA: persist filesChanged so `compose qa-scope` can read them post-build.
|
|
1832
1855
|
// Bug mode skips feature-json — bugs don't have feature.json (COMP-FIX-HARD T4).
|
|
1833
1856
|
if (!isBugMode) {
|
|
1834
|
-
|
|
1857
|
+
const _bp = await getBuildProvider(cwd);
|
|
1858
|
+
// Guard: feature.json may not exist when triage was skipped (test harnesses).
|
|
1859
|
+
// Original updateFeature silently no-oped when feature was missing.
|
|
1860
|
+
// Single atomic raw write (status + filesChanged together) — restores original
|
|
1861
|
+
// updateFeature atomicity. persistFeatureRaw: no policy, no events, no roadmap.
|
|
1862
|
+
const _feat = await _bp.getFeature(featureCode);
|
|
1863
|
+
if (_feat) {
|
|
1864
|
+
await _bp.persistFeatureRaw(featureCode, { ..._feat, status: 'COMPLETE', filesChanged: context.filesChanged ?? [] });
|
|
1865
|
+
}
|
|
1835
1866
|
}
|
|
1836
1867
|
const termState = readActiveBuild(dataDir);
|
|
1837
1868
|
if (termState) {
|
|
@@ -1842,7 +1873,15 @@ export async function runBuild(featureCode, opts = {}) {
|
|
|
1842
1873
|
buildStatus = 'killed';
|
|
1843
1874
|
console.log('\nBuild killed.');
|
|
1844
1875
|
await visionWriter.updateItemStatus(itemId, 'killed');
|
|
1845
|
-
if (!isBugMode)
|
|
1876
|
+
if (!isBugMode) {
|
|
1877
|
+
const _bp = await getBuildProvider(cwd);
|
|
1878
|
+
const _feat = await _bp.getFeature(featureCode);
|
|
1879
|
+
if (_feat) {
|
|
1880
|
+
// Raw write back to PLANNED — no transition policy, no events, no renderRoadmap.
|
|
1881
|
+
// Matches original updateFeature semantics; keeps teardown side-effect-free.
|
|
1882
|
+
await _bp.persistFeatureRaw(featureCode, { ..._feat, status: 'PLANNED' });
|
|
1883
|
+
}
|
|
1884
|
+
}
|
|
1846
1885
|
const termState = readActiveBuild(dataDir);
|
|
1847
1886
|
if (termState) {
|
|
1848
1887
|
writeActiveBuild(dataDir, { ...termState, status: 'aborted', completedAt: new Date().toISOString() });
|
|
@@ -1851,7 +1890,15 @@ export async function runBuild(featureCode, opts = {}) {
|
|
|
1851
1890
|
// Ship failure or other explicit failure — write terminal state
|
|
1852
1891
|
console.log('\nBuild failed.');
|
|
1853
1892
|
await visionWriter.updateItemStatus(itemId, 'failed');
|
|
1854
|
-
if (!isBugMode)
|
|
1893
|
+
if (!isBugMode) {
|
|
1894
|
+
const _bp = await getBuildProvider(cwd);
|
|
1895
|
+
const _feat = await _bp.getFeature(featureCode);
|
|
1896
|
+
if (_feat) {
|
|
1897
|
+
// Raw write back to PLANNED — no transition policy, no events, no renderRoadmap.
|
|
1898
|
+
// Matches original updateFeature semantics; keeps teardown side-effect-free.
|
|
1899
|
+
await _bp.persistFeatureRaw(featureCode, { ..._feat, status: 'PLANNED' });
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1855
1902
|
const termState = readActiveBuild(dataDir);
|
|
1856
1903
|
if (termState) {
|
|
1857
1904
|
writeActiveBuild(dataDir, { ...termState, status: 'failed', completedAt: new Date().toISOString() });
|
package/lib/changelog-writer.js
CHANGED
|
@@ -20,14 +20,19 @@
|
|
|
20
20
|
* CLI, or future REST routes.
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
|
-
import {
|
|
24
|
-
|
|
25
|
-
} from 'node:fs';
|
|
26
|
-
import { join, dirname } from 'node:path';
|
|
23
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
24
|
+
import { join } from 'node:path';
|
|
27
25
|
|
|
28
|
-
import {
|
|
26
|
+
import { normalizeSince } from './feature-events.js';
|
|
29
27
|
import { checkOrInsert } from './idempotency.js';
|
|
30
28
|
|
|
29
|
+
// providerFor is imported lazily to avoid the load-time cycle:
|
|
30
|
+
// factory.js → local-provider.js → changelog-writer.js
|
|
31
|
+
async function getProvider(cwd) {
|
|
32
|
+
const { providerFor } = await import('./tracker/factory.js');
|
|
33
|
+
return providerFor(cwd);
|
|
34
|
+
}
|
|
35
|
+
|
|
31
36
|
// ---------------------------------------------------------------------------
|
|
32
37
|
// Validation helpers
|
|
33
38
|
// ---------------------------------------------------------------------------
|
|
@@ -304,9 +309,13 @@ function readChangelogText(cwd) {
|
|
|
304
309
|
return readFileSync(p, 'utf-8');
|
|
305
310
|
}
|
|
306
311
|
|
|
307
|
-
|
|
312
|
+
// Routes through provider.appendEvent so GitHubProvider can post
|
|
313
|
+
// <!--compose-event--> comments. LocalFileProvider delegates to
|
|
314
|
+
// feature-events.js#appendEvent producing byte-identical output.
|
|
315
|
+
async function safeAppendEvent(cwd, event) {
|
|
308
316
|
try {
|
|
309
|
-
|
|
317
|
+
const provider = await getProvider(cwd);
|
|
318
|
+
await provider.appendEvent(event.code, event);
|
|
310
319
|
} catch (err) {
|
|
311
320
|
// eslint-disable-next-line no-console
|
|
312
321
|
console.warn(`[changelog-writer] audit append failed for ${event.tool} ${event.code ?? ''}: ${err.message}`);
|
|
@@ -320,17 +329,86 @@ function maybeIdempotent(args, fn) {
|
|
|
320
329
|
return Promise.resolve().then(fn);
|
|
321
330
|
}
|
|
322
331
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
332
|
+
/**
|
|
333
|
+
* spliceChangelog(currentText, entry) — pure string-in / string-out transform.
|
|
334
|
+
*
|
|
335
|
+
* Parses `currentText`, applies the mutation described by `entry` (same shape
|
|
336
|
+
* as the `args` to addChangelogEntry), and returns the new file content.
|
|
337
|
+
*
|
|
338
|
+
* Exported so future providers (e.g. GitHubProvider) can fetch a remote blob,
|
|
339
|
+
* pass it here, then PUT the result back — without touching the local FS.
|
|
340
|
+
*
|
|
341
|
+
* Throws formatError if the file exists but lacks the `# Changelog` header.
|
|
342
|
+
* Returns { content, insertedAtLine, surface, action } — callers write content.
|
|
343
|
+
*/
|
|
344
|
+
export function spliceChangelog(currentText, entry) {
|
|
345
|
+
const text = currentText;
|
|
346
|
+
if (text.length > 0 && !H1_RE.test(text.split('\n')[0] ?? '')) {
|
|
347
|
+
throw formatError('first line must be "# Changelog" (line 1)');
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const parsed = parseChangelog(text);
|
|
351
|
+
|
|
352
|
+
// Find existing entry across all matching surfaces.
|
|
353
|
+
const matchingSurfaces = parsed.surfaces.filter(s => s.label === entry.date_or_version);
|
|
354
|
+
let existingSurface = null;
|
|
355
|
+
let existingEntry = null;
|
|
356
|
+
for (const s of matchingSurfaces) {
|
|
357
|
+
const e = s.entries.find(en => en.code === entry.code);
|
|
358
|
+
if (e) { existingSurface = s; existingEntry = e; break; }
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const rendered = renderEntry({
|
|
362
|
+
code: entry.code,
|
|
363
|
+
summary: entry.summary,
|
|
364
|
+
body: entry.body,
|
|
365
|
+
sections: entry.sections,
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
let action;
|
|
369
|
+
let chosenSurfaceLabel = entry.date_or_version;
|
|
370
|
+
let chosenSurfaceStartLine = -1;
|
|
371
|
+
|
|
372
|
+
if (existingEntry && !entry.force) {
|
|
373
|
+
// Storage-level idempotent no-op — caller decides what to do with this.
|
|
374
|
+
return {
|
|
375
|
+
content: null,
|
|
376
|
+
insertedAtLine: existingEntry.startLine,
|
|
377
|
+
surface: existingSurface.label,
|
|
378
|
+
idempotent: true,
|
|
379
|
+
};
|
|
333
380
|
}
|
|
381
|
+
|
|
382
|
+
if (existingEntry && entry.force) {
|
|
383
|
+
action = { kind: 'replace', entry: existingEntry, code: entry.code };
|
|
384
|
+
chosenSurfaceLabel = existingSurface.label;
|
|
385
|
+
chosenSurfaceStartLine = existingSurface.startLine;
|
|
386
|
+
} else if (matchingSurfaces.length > 0) {
|
|
387
|
+
const surface = matchingSurfaces[0];
|
|
388
|
+
action = { kind: 'append-to-surface', surface, code: entry.code };
|
|
389
|
+
chosenSurfaceLabel = surface.label;
|
|
390
|
+
chosenSurfaceStartLine = surface.startLine;
|
|
391
|
+
} else if (text.length === 0) {
|
|
392
|
+
action = { kind: 'new-file', code: entry.code };
|
|
393
|
+
chosenSurfaceStartLine = 3;
|
|
394
|
+
} else {
|
|
395
|
+
action = { kind: 'new-surface', code: entry.code };
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const { content, insertedAtLine } = buildNextContent(text, parsed, action, rendered, entry.date_or_version);
|
|
399
|
+
|
|
400
|
+
// For new-surface, recompute chosenSurfaceStartLine from output.
|
|
401
|
+
if (action.kind === 'new-surface' || action.kind === 'new-file') {
|
|
402
|
+
const outLines = content.split('\n');
|
|
403
|
+
for (let i = 0; i < outLines.length; i++) {
|
|
404
|
+
if (outLines[i] === `## ${entry.date_or_version}`) {
|
|
405
|
+
chosenSurfaceStartLine = i + 1;
|
|
406
|
+
break;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
return { content, insertedAtLine, surface: chosenSurfaceLabel, idempotent: false, action, chosenSurfaceStartLine };
|
|
334
412
|
}
|
|
335
413
|
|
|
336
414
|
/**
|
|
@@ -507,91 +585,41 @@ export async function addChangelogEntry(cwd, args) {
|
|
|
507
585
|
validateSummary(args.summary);
|
|
508
586
|
validateSections(args.sections);
|
|
509
587
|
|
|
510
|
-
return maybeIdempotent({ ...args, cwd }, () => {
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
588
|
+
return maybeIdempotent({ ...args, cwd }, async () => {
|
|
589
|
+
// Route through provider low-level primitives (getChangelog / putChangelog).
|
|
590
|
+
// Do NOT call provider.appendChangelog — that's LocalFileProvider→addChangelogEntry = recursion.
|
|
591
|
+
const provider = await getProvider(cwd);
|
|
592
|
+
const text = await provider.getChangelog();
|
|
515
593
|
|
|
516
|
-
const
|
|
517
|
-
|
|
518
|
-
// Find existing entry across all matching surfaces.
|
|
519
|
-
const matchingSurfaces = parsed.surfaces.filter(s => s.label === args.date_or_version);
|
|
520
|
-
let existingSurface = null;
|
|
521
|
-
let existingEntry = null;
|
|
522
|
-
for (const s of matchingSurfaces) {
|
|
523
|
-
const e = s.entries.find(en => en.code === args.code);
|
|
524
|
-
if (e) { existingSurface = s; existingEntry = e; break; }
|
|
525
|
-
}
|
|
594
|
+
const spliced = spliceChangelog(text, args);
|
|
526
595
|
|
|
527
|
-
|
|
528
|
-
code: args.code,
|
|
529
|
-
summary: args.summary,
|
|
530
|
-
body: args.body,
|
|
531
|
-
sections: args.sections,
|
|
532
|
-
});
|
|
533
|
-
|
|
534
|
-
let action;
|
|
535
|
-
let chosenSurfaceLabel = args.date_or_version;
|
|
536
|
-
let chosenSurfaceStartLine = -1;
|
|
537
|
-
|
|
538
|
-
if (existingEntry && !args.force) {
|
|
596
|
+
if (spliced.idempotent) {
|
|
539
597
|
// Storage-level idempotent no-op: no file write, no audit event
|
|
540
598
|
// (Decision 2 of design). Caller-supplied idempotency_key replays are
|
|
541
599
|
// handled separately by the maybeIdempotent wrapper above.
|
|
542
600
|
return {
|
|
543
|
-
inserted_at:
|
|
601
|
+
inserted_at: spliced.insertedAtLine,
|
|
544
602
|
idempotent: true,
|
|
545
|
-
surface:
|
|
603
|
+
surface: spliced.surface,
|
|
546
604
|
};
|
|
547
605
|
}
|
|
548
606
|
|
|
549
|
-
|
|
550
|
-
action = { kind: 'replace', entry: existingEntry, code: args.code };
|
|
551
|
-
chosenSurfaceLabel = existingSurface.label;
|
|
552
|
-
chosenSurfaceStartLine = existingSurface.startLine;
|
|
553
|
-
} else if (matchingSurfaces.length > 0) {
|
|
554
|
-
// No existing entry; insert into first (topmost) matching surface.
|
|
555
|
-
const surface = matchingSurfaces[0];
|
|
556
|
-
action = { kind: 'append-to-surface', surface, code: args.code };
|
|
557
|
-
chosenSurfaceLabel = surface.label;
|
|
558
|
-
chosenSurfaceStartLine = surface.startLine;
|
|
559
|
-
} else if (text.length === 0) {
|
|
560
|
-
action = { kind: 'new-file', code: args.code };
|
|
561
|
-
chosenSurfaceStartLine = 3; // line of new surface heading after H1 + blank
|
|
562
|
-
} else {
|
|
563
|
-
action = { kind: 'new-surface', code: args.code };
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
const { content, insertedAtLine } = buildNextContent(text, parsed, action, rendered, args.date_or_version);
|
|
567
|
-
|
|
568
|
-
// For new-surface, recompute chosenSurfaceStartLine from output.
|
|
569
|
-
if (action.kind === 'new-surface' || action.kind === 'new-file') {
|
|
570
|
-
const outLines = content.split('\n');
|
|
571
|
-
for (let i = 0; i < outLines.length; i++) {
|
|
572
|
-
if (outLines[i] === `## ${args.date_or_version}`) {
|
|
573
|
-
chosenSurfaceStartLine = i + 1;
|
|
574
|
-
break;
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
atomicWrite(cwd, content);
|
|
607
|
+
await provider.putChangelog(spliced.content);
|
|
580
608
|
|
|
581
609
|
const event = {
|
|
582
610
|
tool: 'add_changelog_entry',
|
|
583
611
|
code: args.code,
|
|
584
|
-
surface_label:
|
|
585
|
-
surface_start_line: chosenSurfaceStartLine,
|
|
612
|
+
surface_label: spliced.surface,
|
|
613
|
+
surface_start_line: spliced.chosenSurfaceStartLine,
|
|
586
614
|
};
|
|
587
615
|
if (args.idempotency_key) event.idempotency_key = args.idempotency_key;
|
|
588
|
-
if (action
|
|
589
|
-
safeAppendEvent(cwd, event);
|
|
616
|
+
if (spliced.action?.kind === 'replace') event.force = true;
|
|
617
|
+
await safeAppendEvent(cwd, event);
|
|
590
618
|
|
|
591
619
|
return {
|
|
592
|
-
inserted_at: insertedAtLine,
|
|
620
|
+
inserted_at: spliced.insertedAtLine,
|
|
593
621
|
idempotent: false,
|
|
594
|
-
surface:
|
|
622
|
+
surface: spliced.surface,
|
|
595
623
|
};
|
|
596
624
|
});
|
|
597
625
|
}
|
package/lib/completion-writer.js
CHANGED
|
@@ -24,12 +24,19 @@
|
|
|
24
24
|
import { mkdirSync, rmSync, statSync } from 'fs';
|
|
25
25
|
import { join, dirname, posix } from 'path';
|
|
26
26
|
|
|
27
|
-
import { readFeature,
|
|
27
|
+
import { readFeature, listFeatures } from './feature-json.js';
|
|
28
28
|
import { loadFeaturesDir } from './project-paths.js';
|
|
29
|
-
import {
|
|
29
|
+
import { normalizeSince } from './feature-events.js';
|
|
30
30
|
import { checkOrInsert } from './idempotency.js';
|
|
31
31
|
import { FEATURE_CODE_RE_STRICT as FEATURE_CODE_RE } from './feature-code.js';
|
|
32
32
|
|
|
33
|
+
// providerFor is imported lazily to avoid the load-time cycle:
|
|
34
|
+
// factory.js → local-provider.js → completion-writer.js
|
|
35
|
+
async function getProvider(cwd) {
|
|
36
|
+
const { providerFor } = await import('./tracker/factory.js');
|
|
37
|
+
return providerFor(cwd);
|
|
38
|
+
}
|
|
39
|
+
|
|
33
40
|
// ---------------------------------------------------------------------------
|
|
34
41
|
// Constants + regexes
|
|
35
42
|
// ---------------------------------------------------------------------------
|
|
@@ -206,11 +213,17 @@ function maybeIdempotent(args, fn) {
|
|
|
206
213
|
// ---------------------------------------------------------------------------
|
|
207
214
|
// safeAppendEvent — best-effort; failed append must NOT roll back a committed
|
|
208
215
|
// mutation (per sibling-writer convention).
|
|
216
|
+
//
|
|
217
|
+
// Routes through provider.appendEvent so GitHubProvider can post
|
|
218
|
+
// <!--compose-event--> comments. LocalFileProvider delegates to
|
|
219
|
+
// feature-events.js#appendEvent producing byte-identical output.
|
|
209
220
|
// ---------------------------------------------------------------------------
|
|
210
221
|
|
|
211
|
-
function safeAppendEvent(cwd, event) {
|
|
222
|
+
async function safeAppendEvent(cwd, event) {
|
|
212
223
|
try {
|
|
213
|
-
|
|
224
|
+
const { providerFor } = await import('./tracker/factory.js');
|
|
225
|
+
const provider = await providerFor(cwd);
|
|
226
|
+
await provider.appendEvent(event.code, event);
|
|
214
227
|
} catch (err) {
|
|
215
228
|
// eslint-disable-next-line no-console
|
|
216
229
|
console.warn(
|
|
@@ -252,14 +265,16 @@ export async function recordCompletion(cwd, args) {
|
|
|
252
265
|
// 3. Compute completion_id
|
|
253
266
|
const completion_id = `${feature_code}:${commit_sha}`;
|
|
254
267
|
|
|
255
|
-
const featuresDir = loadFeaturesDir(cwd);
|
|
256
268
|
// 4. Wrap in maybeIdempotent for caller-key path
|
|
257
269
|
return maybeIdempotent({ ...args, cwd }, async () => {
|
|
270
|
+
// Acquire provider once per operation (resolves factory, no per-call overhead)
|
|
271
|
+
const provider = await getProvider(cwd);
|
|
272
|
+
|
|
258
273
|
// 5a. Acquire per-feature advisory lock (Decision 10)
|
|
259
274
|
const release = await acquireFeatureLock(cwd, feature_code);
|
|
260
275
|
try {
|
|
261
|
-
// 5b. Read feature
|
|
262
|
-
const feature =
|
|
276
|
+
// 5b. Read feature via provider (low-level primitive — no recursion risk)
|
|
277
|
+
const feature = await provider.getFeature(feature_code);
|
|
263
278
|
if (!feature) throw notFoundError(feature_code);
|
|
264
279
|
|
|
265
280
|
// 5c. Snapshot completions array
|
|
@@ -303,7 +318,9 @@ export async function recordCompletion(cwd, args) {
|
|
|
303
318
|
}
|
|
304
319
|
|
|
305
320
|
// 5h. Persist completion record BEFORE status flip (so flip failure doesn't lose the record)
|
|
306
|
-
|
|
321
|
+
// Use persistFeatureRaw (policy-free, no status-delta guard) — completion-writer
|
|
322
|
+
// owns its own ordering/locking and only mutates the completions array, not status.
|
|
323
|
+
await provider.persistFeatureRaw(feature_code, { ...feature, completions });
|
|
307
324
|
|
|
308
325
|
// 5i. Status flip (default on)
|
|
309
326
|
const set_status = args.set_status !== false;
|
|
@@ -357,7 +374,7 @@ export async function recordCompletion(cwd, args) {
|
|
|
357
374
|
};
|
|
358
375
|
if (args.force) auditEvent.force = args.force;
|
|
359
376
|
if (args.idempotency_key) auditEvent.idempotency_key = args.idempotency_key;
|
|
360
|
-
safeAppendEvent(cwd, auditEvent);
|
|
377
|
+
await safeAppendEvent(cwd, auditEvent);
|
|
361
378
|
|
|
362
379
|
// 5l. Return
|
|
363
380
|
return {
|