@applica-software-guru/sdd-core 1.7.0 → 1.8.1
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/dist/remote/api-client.d.ts +7 -1
- package/dist/remote/api-client.d.ts.map +1 -1
- package/dist/remote/api-client.js +15 -0
- package/dist/remote/api-client.js.map +1 -1
- package/dist/remote/sync-engine.d.ts.map +1 -1
- package/dist/remote/sync-engine.js +153 -30
- package/dist/remote/sync-engine.js.map +1 -1
- package/dist/remote/types.d.ts +9 -0
- package/dist/remote/types.d.ts.map +1 -1
- package/dist/sdd.js +1 -1
- package/dist/sdd.js.map +1 -1
- package/package.json +1 -1
- package/src/remote/api-client.ts +25 -0
- package/src/remote/sync-engine.ts +168 -32
- package/src/remote/types.ts +10 -0
- package/src/sdd.ts +1 -1
- package/tests/apply.test.ts +12 -14
- package/tests/bug.test.ts +22 -0
- package/tests/integration.test.ts +1 -1
- package/tests/prompt.test.ts +3 -5
- package/tests/remote-state.test.ts +2 -2
- package/tests/sync-engine.test.ts +156 -5
package/dist/sdd.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sdd.js","sourceRoot":"","sources":["../src/sdd.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"sdd.js","sourceRoot":"","sources":["../src/sdd.ts"],"names":[],"mappings":";;;AAAA,+CAAuD;AACvD,yCAAoC;AAEpC,2CAAyD;AACzD,8DAA8D;AAC9D,sEAA8D;AAC9D,kFAAyE;AACzE,kFAAmF;AACnF,0DAAmD;AACnD,gDAAiD;AACjD,kEAAmF;AACnF,wDAAwD;AACxD,0DAA0D;AAE1D,oEAKsC;AACtC,4DAOiC;AAGjC,MAAa,GAAG;IACN,IAAI,CAAS;IAErB,YAAY,OAAyB;QACnC,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAC3B,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,IAAkB;QAC3B,OAAO,IAAA,qBAAW,EAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACtC,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,OAA6B;QAC9C,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACzB,OAAO,IAAA,qCAAiB,EAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAC/C,CAAC;IAED,iBAAiB;QACf,OAAO,IAAA,yCAAqB,GAAE,CAAC;IACjC,CAAC;IAED,KAAK,CAAC,MAAM;QACV,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACzB,OAAO,IAAA,8BAAU,EAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC/B,CAAC;IAED,KAAK,CAAC,MAAM;QACV,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACzB,MAAM,KAAK,GAAG,MAAM,IAAA,oCAAkB,EAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAElD,OAAO;YACL,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBACvB,YAAY,EAAE,CAAC,CAAC,YAAY;gBAC5B,MAAM,EAAE,CAAC,CAAC,WAAW,CAAC,MAAM;gBAC5B,OAAO,EAAE,CAAC,CAAC,WAAW,CAAC,OAAO;gBAC9B,YAAY,EAAE,CAAC,CAAC,WAAW,CAAC,eAAe,CAAC;aAC7C,CAAC,CAAC;SACJ,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,OAAO;QACX,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACzB,MAAM,KAAK,GAAG,MAAM,IAAA,oCAAkB,EAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClD,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC,MAAM,KAAK,QAAQ,IAAI,CAAC,CAAC,WAAW,CAAC,MAAM,KAAK,OAAO,CAAC,CAAC;IACpG,CAAC;IAED,KAAK,CAAC,IAAI;QACR,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QACrC,OAAO,IAAA,oCAAc,EAAC,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;IAC5C,CAAC;IAED,KAAK,CAAC,WAAW;QACf,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACzB,MAAM,CAAC,IAAI,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;YACvF,IAAI,CAAC,QAAQ,EAAE;YACf,IAAI,CAAC,qBAAqB,EAAE;YAC5B,IAAI,CAAC,OAAO,EAAE;YACd,IAAI,CAAC,MAAM,EAAE;YACb,IAAA,oCAAkB,EAAC,IAAI,CAAC,IAAI,CAAC;YAC7B,IAAA,8BAAU,EAAC,IAAI,CAAC,IAAI,CAAC;SACtB,CAAC,CAAC;QACH,MAAM,cAAc,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC,MAAM,KAAK,OAAO,CAAC,CAAC;QAChF,OAAO,IAAA,+CAAmB,EAAC,IAAI,EAAE,cAAc,EAAE,YAAY,EAAE,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC;IACxH,CAAC;IAED,KAAK,CAAC,QAAQ;QACZ,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACzB,MAAM,KAAK,GAAG,MAAM,IAAA,oCAAkB,EAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClD,OAAO,IAAA,uBAAQ,EAAC,KAAK,CAAC,CAAC;IACzB,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,KAAgB;QAC/B,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACzB,MAAM,KAAK,GAAG,MAAM,IAAA,oCAAkB,EAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClD,MAAM,MAAM,GAAa,EAAE,CAAC;QAE5B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC,WAAW,CAAC;YACpC,IAAI,MAAM,KAAK,QAAQ;gBAAE,SAAS;YAClC,IAAI,KAAK,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,YAAY,CAAC;gBAAE,SAAS;YAE9E,MAAM,OAAO,GAAG,IAAA,mBAAO,EAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;YAEtD,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;gBACzB,uCAAuC;gBACvC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,CAAC;gBACpD,MAAM,MAAM,CAAC,OAAO,CAAC,CAAC;gBACtB,MAAM,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,YAAY,YAAY,CAAC,CAAC;YAChD,CAAC;iBAAM,CAAC;gBACN,0BAA0B;gBAC1B,MAAM,OAAO,GAAG,MAAM,IAAA,mBAAQ,EAAC,OAAO,EAAE,OAAO,CAAC,CAAC;gBACjD,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,2BAA2B,EAAE,gBAAgB,CAAC,CAAC;gBAC/E,MAAM,IAAA,oBAAS,EAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;gBAC3C,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;YACjC,CAAC;QACH,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,KAAK,CAAC,cAAc;QAClB,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACzB,OAAO,IAAA,8BAAe,EAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACpC,CAAC;IAED,KAAK,CAAC,qBAAqB;QACzB,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,cAAc,EAAE,CAAC;QACxC,OAAO,GAAG,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,WAAW,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC;IACjE,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,KAAgB;QAClC,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACzB,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,cAAc,EAAE,CAAC;QACxC,MAAM,MAAM,GAAa,EAAE,CAAC;QAE5B,KAAK,MAAM,EAAE,IAAI,GAAG,EAAE,CAAC;YACrB,IAAI,EAAE,CAAC,WAAW,CAAC,MAAM,KAAK,SAAS;gBAAE,SAAS;YAClD,IAAI,KAAK,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC,YAAY,CAAC;gBAAE,SAAS;YAE5E,MAAM,OAAO,GAAG,IAAA,mBAAO,EAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC,YAAY,CAAC,CAAC;YACpD,MAAM,OAAO,GAAG,MAAM,IAAA,mBAAQ,EAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YACjD,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,qBAAqB,EAAE,iBAAiB,CAAC,CAAC;YAC1E,MAAM,IAAA,oBAAS,EAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;YAC3C,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,YAAY,CAAC,CAAC;QAC/B,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,KAAK,CAAC,IAAI;QACR,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACzB,OAAO,IAAA,gCAAgB,EAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACrC,CAAC;IAED,KAAK,CAAC,QAAQ;QACZ,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QAC9B,OAAO,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC;IAC5D,CAAC;IAED,KAAK,CAAC,eAAe,CAAC,KAAgB;QACpC,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACzB,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QAC9B,MAAM,MAAM,GAAa,EAAE,CAAC;QAE5B,KAAK,MAAM,GAAG,IAAI,GAAG,EAAE,CAAC;YACtB,IAAI,GAAG,CAAC,WAAW,CAAC,MAAM,KAAK,MAAM;gBAAE,SAAS;YAChD,IAAI,KAAK,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,YAAY,CAAC;gBAAE,SAAS;YAE7E,MAAM,OAAO,GAAG,IAAA,mBAAO,EAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,YAAY,CAAC,CAAC;YACrD,MAAM,OAAO,GAAG,MAAM,IAAA,mBAAQ,EAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YACjD,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,kBAAkB,EAAE,kBAAkB,CAAC,CAAC;YACxE,MAAM,IAAA,oBAAS,EAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;YAC3C,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;QAChC,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,wEAAwE;IAExE,KAAK,CAAC,MAAM;QACV,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACzB,MAAM,CAAC,KAAK,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;YAC3C,IAAA,oCAAkB,EAAC,IAAI,CAAC,IAAI,CAAC;YAC7B,IAAI,CAAC,cAAc,EAAE;YACrB,IAAI,CAAC,IAAI,EAAE;SACZ,CAAC,CAAC;QACH,OAAO;YACL,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC,MAAM,KAAK,OAAO,CAAC;YAC3D,GAAG,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,WAAW,CAAC,MAAM,KAAK,OAAO,CAAC;YAC1D,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC,MAAM,KAAK,OAAO,CAAC;SAC3D,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,qBAAqB;QACzB,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACzB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM,EAAE,CAAC;QACnC,MAAM,QAAQ,GAAG,MAAM,IAAA,oCAAkB,EAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACrD,MAAM,cAAc,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC,MAAM,KAAK,OAAO,CAAC,CAAC;QAChF,MAAM,MAAM,GAAG,MAAM,IAAA,8BAAU,EAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC3C,OAAO,IAAA,yDAA6B,EAAC,MAAM,EAAE,cAAc,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC;IACnF,CAAC;IAED,KAAK,CAAC,kBAAkB,CAAC,KAAgB;QACvC,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACzB,MAAM,MAAM,GAAa,EAAE,CAAC;QAE5B,2BAA2B;QAC3B,MAAM,KAAK,GAAG,MAAM,IAAA,oCAAkB,EAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,IAAI,IAAI,CAAC,WAAW,CAAC,MAAM,KAAK,OAAO;gBAAE,SAAS;YAClD,IAAI,KAAK,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,YAAY,CAAC;gBAAE,SAAS;YAC9E,MAAM,OAAO,GAAG,IAAA,mBAAO,EAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;YACtD,MAAM,OAAO,GAAG,MAAM,IAAA,mBAAQ,EAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YACjD,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,mBAAmB,EAAE,aAAa,CAAC,CAAC;YACpE,MAAM,IAAA,oBAAS,EAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;YAC3C,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QACjC,CAAC;QAED,uBAAuB;QACvB,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,cAAc,EAAE,CAAC;QACxC,KAAK,MAAM,EAAE,IAAI,GAAG,EAAE,CAAC;YACrB,IAAI,EAAE,CAAC,WAAW,CAAC,MAAM,KAAK,OAAO;gBAAE,SAAS;YAChD,IAAI,KAAK,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC,YAAY,CAAC;gBAAE,SAAS;YAC5E,MAAM,OAAO,GAAG,IAAA,mBAAO,EAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC,YAAY,CAAC,CAAC;YACpD,MAAM,OAAO,GAAG,MAAM,IAAA,mBAAQ,EAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YACjD,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,mBAAmB,EAAE,iBAAiB,CAAC,CAAC;YACxE,MAAM,IAAA,oBAAS,EAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;YAC3C,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,YAAY,CAAC,CAAC;QAC/B,CAAC;QAED,qBAAqB;QACrB,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QAClC,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;YAC1B,IAAI,GAAG,CAAC,WAAW,CAAC,MAAM,KAAK,OAAO;gBAAE,SAAS;YACjD,IAAI,KAAK,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,YAAY,CAAC;gBAAE,SAAS;YAC7E,MAAM,OAAO,GAAG,IAAA,mBAAO,EAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,YAAY,CAAC,CAAC;YACrD,MAAM,OAAO,GAAG,MAAM,IAAA,mBAAQ,EAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YACjD,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,mBAAmB,EAAE,cAAc,CAAC,CAAC;YACrE,MAAM,IAAA,oBAAS,EAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;YAC3C,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;QAChC,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,wEAAwE;IAExE,KAAK,CAAC,YAAY,CAAC,OAAgB;QACjC,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACzB,OAAO,IAAA,gCAAe,EAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAC7C,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,KAAgB,EAAE,OAA6C;QACxE,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACzB,OAAO,IAAA,6BAAY,EAAC,IAAI,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;IAC1F,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,OAAgB;QACzB,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACzB,OAAO,IAAA,+BAAc,EAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAC5C,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,OAAgB;QAC5B,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACzB,OAAO,IAAA,kCAAiB,EAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAC/C,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,OAAgB;QAC7B,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACzB,OAAO,IAAA,mCAAkB,EAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAChD,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,WAAmB,EAAE,OAAgB;QACrD,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACzB,OAAO,IAAA,mCAAkB,EAAC,IAAI,CAAC,IAAI,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC;IAC7D,CAAC;IAEO,iBAAiB;QACvB,IAAI,CAAC,IAAA,gCAAY,EAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAC7B,MAAM,IAAI,sCAA0B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClD,CAAC;IACH,CAAC;CACF;AAtQD,kBAsQC"}
|
package/package.json
CHANGED
package/src/remote/api-client.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
RemoteCRBulkResponse,
|
|
8
8
|
RemoteBugResponse,
|
|
9
9
|
RemoteBugBulkResponse,
|
|
10
|
+
RemoteDeleteResponse,
|
|
10
11
|
RemoteResetResult,
|
|
11
12
|
} from './types.js';
|
|
12
13
|
|
|
@@ -204,6 +205,30 @@ export async function pushBugs(
|
|
|
204
205
|
return request<RemoteBugBulkResponse>(config, 'POST', '/cli/push-bugs', { bugs });
|
|
205
206
|
}
|
|
206
207
|
|
|
208
|
+
/** POST /cli/delete-docs */
|
|
209
|
+
export async function deleteDocs(
|
|
210
|
+
config: ApiClientConfig,
|
|
211
|
+
paths: string[],
|
|
212
|
+
): Promise<RemoteDeleteResponse> {
|
|
213
|
+
return request<RemoteDeleteResponse>(config, 'POST', '/cli/delete-docs', { paths });
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** POST /cli/delete-crs */
|
|
217
|
+
export async function deleteCRs(
|
|
218
|
+
config: ApiClientConfig,
|
|
219
|
+
paths: string[],
|
|
220
|
+
): Promise<RemoteDeleteResponse> {
|
|
221
|
+
return request<RemoteDeleteResponse>(config, 'POST', '/cli/delete-crs', { paths });
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** POST /cli/delete-bugs */
|
|
225
|
+
export async function deleteBugs(
|
|
226
|
+
config: ApiClientConfig,
|
|
227
|
+
paths: string[],
|
|
228
|
+
): Promise<RemoteDeleteResponse> {
|
|
229
|
+
return request<RemoteDeleteResponse>(config, 'POST', '/cli/delete-bugs', { paths });
|
|
230
|
+
}
|
|
231
|
+
|
|
207
232
|
/** POST /cli/crs/:crId/applied */
|
|
208
233
|
export async function markCRAppliedRemote(
|
|
209
234
|
config: ApiClientConfig,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readFile, writeFile, mkdir } from 'node:fs/promises';
|
|
1
|
+
import { readFile, writeFile, mkdir, unlink } from 'node:fs/promises';
|
|
2
2
|
import { existsSync } from 'node:fs';
|
|
3
3
|
import { resolve, dirname, posix } from 'node:path';
|
|
4
4
|
import { createHash } from 'node:crypto';
|
|
@@ -8,7 +8,7 @@ import { readConfig } from '../config/config-manager.js';
|
|
|
8
8
|
import { parseAllStoryFiles } from '../parser/story-parser.js';
|
|
9
9
|
import { parseAllCRFiles } from '../parser/cr-parser.js';
|
|
10
10
|
import { parseAllBugFiles } from '../parser/bug-parser.js';
|
|
11
|
-
import { buildApiConfig, pullDocs, pushDocs, pushCRs, pushBugs, fetchPendingCRs, fetchOpenBugs, resetProject } from './api-client.js';
|
|
11
|
+
import { buildApiConfig, pullDocs, pushDocs, pushCRs, pushBugs, deleteDocs, deleteCRs, deleteBugs, fetchPendingCRs, fetchOpenBugs, resetProject } from './api-client.js';
|
|
12
12
|
import { readRemoteState, writeRemoteState } from './state.js';
|
|
13
13
|
import type {
|
|
14
14
|
PushResult,
|
|
@@ -100,12 +100,24 @@ export interface PushOptions {
|
|
|
100
100
|
timeout?: number;
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
function isReseedPush(state: RemoteStatusTrackingState, options?: PushOptions): boolean {
|
|
104
|
+
return state.needsReseed === true && !(options?.paths && options.paths.length > 0);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
type RemoteStatusTrackingState = {
|
|
108
|
+
needsReseed?: boolean;
|
|
109
|
+
documents: Record<string, { remoteId: string; remoteVersion: number; localHash: string; lastSynced: string }>;
|
|
110
|
+
changeRequests?: Record<string, { remoteId: string; localHash: string; lastSynced: string }>;
|
|
111
|
+
bugs?: Record<string, { remoteId: string; localHash: string; lastSynced: string }>;
|
|
112
|
+
};
|
|
113
|
+
|
|
103
114
|
export async function pushToRemote(root: string, options?: PushOptions): Promise<PushResult> {
|
|
104
115
|
const config = await readConfig(root);
|
|
105
116
|
const api = buildApiConfig(config, options?.timeout);
|
|
106
117
|
const state = await readRemoteState(root);
|
|
107
118
|
if (!state.changeRequests) state.changeRequests = {};
|
|
108
119
|
if (!state.bugs) state.bugs = {};
|
|
120
|
+
const reseedPush = isReseedPush(state, options);
|
|
109
121
|
|
|
110
122
|
let totalCreated = 0;
|
|
111
123
|
let totalUpdated = 0;
|
|
@@ -115,8 +127,11 @@ export async function pushToRemote(root: string, options?: PushOptions): Promise
|
|
|
115
127
|
const files = await parseAllStoryFiles(root);
|
|
116
128
|
const toPush = files.filter((f) => {
|
|
117
129
|
if (options?.paths && options.paths.length > 0) return options.paths.includes(f.relativePath);
|
|
130
|
+
if (reseedPush) return true;
|
|
118
131
|
if (options?.all) return true;
|
|
119
|
-
|
|
132
|
+
const tracked = state.documents[normalizePath(f.relativePath)];
|
|
133
|
+
if (!tracked) return true;
|
|
134
|
+
return tracked.localHash !== f.hash;
|
|
120
135
|
});
|
|
121
136
|
|
|
122
137
|
if (toPush.length > 0) {
|
|
@@ -129,7 +144,7 @@ export async function pushToRemote(root: string, options?: PushOptions): Promise
|
|
|
129
144
|
const result = await pushDocs(api, documents);
|
|
130
145
|
|
|
131
146
|
for (const doc of result.documents) {
|
|
132
|
-
const localPath = doc.path;
|
|
147
|
+
const localPath = normalizePath(doc.path);
|
|
133
148
|
const absPath = resolve(root, localPath);
|
|
134
149
|
const rawContent = existsSync(absPath) ? await readFile(absPath, 'utf-8') : '';
|
|
135
150
|
state.documents[localPath] = {
|
|
@@ -140,17 +155,6 @@ export async function pushToRemote(root: string, options?: PushOptions): Promise
|
|
|
140
155
|
};
|
|
141
156
|
}
|
|
142
157
|
|
|
143
|
-
// Mark local files as synced (drafts are excluded — they need AI enrichment first)
|
|
144
|
-
for (const f of toPush) {
|
|
145
|
-
if (f.frontmatter.status === 'draft') continue;
|
|
146
|
-
const absPath = resolve(root, f.relativePath);
|
|
147
|
-
const content = await readFile(absPath, 'utf-8');
|
|
148
|
-
const updated = content.replace(/^status:\s*(new|changed)/m, 'status: synced');
|
|
149
|
-
if (updated !== content) {
|
|
150
|
-
await writeFile(absPath, updated, 'utf-8');
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
158
|
totalCreated += result.created;
|
|
155
159
|
totalUpdated += result.updated;
|
|
156
160
|
allPushed.push(...toPush.map((f) => f.relativePath));
|
|
@@ -158,10 +162,21 @@ export async function pushToRemote(root: string, options?: PushOptions): Promise
|
|
|
158
162
|
|
|
159
163
|
// ── Change Requests ────────────────────────────────────────────────────
|
|
160
164
|
const crFiles = await parseAllCRFiles(root);
|
|
165
|
+
const crHashes = new Map<string, string>();
|
|
166
|
+
for (const cr of crFiles) {
|
|
167
|
+
const absPath = resolve(root, cr.relativePath);
|
|
168
|
+
const raw = existsSync(absPath) ? await readFile(absPath, 'utf-8') : '';
|
|
169
|
+
crHashes.set(normalizePath(cr.relativePath), sha256(raw));
|
|
170
|
+
}
|
|
161
171
|
const crsToPush = crFiles.filter((cr) => {
|
|
162
172
|
if (options?.paths && options.paths.length > 0) return options.paths.includes(cr.relativePath);
|
|
173
|
+
if (reseedPush) return true;
|
|
163
174
|
if (options?.all) return true;
|
|
164
|
-
|
|
175
|
+
const key = normalizePath(cr.relativePath);
|
|
176
|
+
const tracked = state.changeRequests![key];
|
|
177
|
+
const localHash = crHashes.get(key);
|
|
178
|
+
if (!tracked || !localHash) return true;
|
|
179
|
+
return tracked.localHash !== localHash;
|
|
165
180
|
});
|
|
166
181
|
|
|
167
182
|
if (crsToPush.length > 0) {
|
|
@@ -200,10 +215,21 @@ export async function pushToRemote(root: string, options?: PushOptions): Promise
|
|
|
200
215
|
|
|
201
216
|
// ── Bugs ───────────────────────────────────────────────────────────────
|
|
202
217
|
const bugFiles = await parseAllBugFiles(root);
|
|
218
|
+
const bugHashes = new Map<string, string>();
|
|
219
|
+
for (const bug of bugFiles) {
|
|
220
|
+
const absPath = resolve(root, bug.relativePath);
|
|
221
|
+
const raw = existsSync(absPath) ? await readFile(absPath, 'utf-8') : '';
|
|
222
|
+
bugHashes.set(normalizePath(bug.relativePath), sha256(raw));
|
|
223
|
+
}
|
|
203
224
|
const bugsToPush = bugFiles.filter((bug) => {
|
|
204
225
|
if (options?.paths && options.paths.length > 0) return options.paths.includes(bug.relativePath);
|
|
226
|
+
if (reseedPush) return true;
|
|
205
227
|
if (options?.all) return true;
|
|
206
|
-
|
|
228
|
+
const key = normalizePath(bug.relativePath);
|
|
229
|
+
const tracked = state.bugs![key];
|
|
230
|
+
const localHash = bugHashes.get(key);
|
|
231
|
+
if (!tracked || !localHash) return true;
|
|
232
|
+
return tracked.localHash !== localHash;
|
|
207
233
|
});
|
|
208
234
|
|
|
209
235
|
if (bugsToPush.length > 0) {
|
|
@@ -240,14 +266,54 @@ export async function pushToRemote(root: string, options?: PushOptions): Promise
|
|
|
240
266
|
allPushed.push(...bugsToPush.map((bug) => bug.relativePath));
|
|
241
267
|
}
|
|
242
268
|
|
|
269
|
+
// ── Detect local deletions (tracked in state but missing from disk) ──
|
|
270
|
+
const allDeleted: string[] = [];
|
|
271
|
+
|
|
272
|
+
const deletedDocPaths = Object.keys(state.documents).filter(
|
|
273
|
+
(p) => !existsSync(resolve(root, p)),
|
|
274
|
+
);
|
|
275
|
+
if (deletedDocPaths.length > 0) {
|
|
276
|
+
await deleteDocs(api, deletedDocPaths);
|
|
277
|
+
for (const p of deletedDocPaths) {
|
|
278
|
+
delete state.documents[p];
|
|
279
|
+
}
|
|
280
|
+
allDeleted.push(...deletedDocPaths);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const deletedCRPaths = Object.keys(state.changeRequests!).filter(
|
|
284
|
+
(p) => !existsSync(resolve(root, p)),
|
|
285
|
+
);
|
|
286
|
+
if (deletedCRPaths.length > 0) {
|
|
287
|
+
await deleteCRs(api, deletedCRPaths);
|
|
288
|
+
for (const p of deletedCRPaths) {
|
|
289
|
+
delete state.changeRequests![p];
|
|
290
|
+
}
|
|
291
|
+
allDeleted.push(...deletedCRPaths);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const deletedBugPaths = Object.keys(state.bugs!).filter(
|
|
295
|
+
(p) => !existsSync(resolve(root, p)),
|
|
296
|
+
);
|
|
297
|
+
if (deletedBugPaths.length > 0) {
|
|
298
|
+
await deleteBugs(api, deletedBugPaths);
|
|
299
|
+
for (const p of deletedBugPaths) {
|
|
300
|
+
delete state.bugs![p];
|
|
301
|
+
}
|
|
302
|
+
allDeleted.push(...deletedBugPaths);
|
|
303
|
+
}
|
|
304
|
+
|
|
243
305
|
// ── Finalize ───────────────────────────────────────────────────────────
|
|
244
306
|
state.lastPush = new Date().toISOString();
|
|
307
|
+
if (reseedPush) {
|
|
308
|
+
state.needsReseed = false;
|
|
309
|
+
}
|
|
245
310
|
await writeRemoteState(root, state);
|
|
246
311
|
|
|
247
312
|
return {
|
|
248
313
|
created: totalCreated,
|
|
249
314
|
updated: totalUpdated,
|
|
250
315
|
pushed: allPushed,
|
|
316
|
+
deleted: allDeleted,
|
|
251
317
|
};
|
|
252
318
|
}
|
|
253
319
|
|
|
@@ -262,8 +328,36 @@ export async function pullFromRemote(root: string, timeout?: number): Promise<Pu
|
|
|
262
328
|
|
|
263
329
|
const created: string[] = [];
|
|
264
330
|
const updated: string[] = [];
|
|
331
|
+
const deleted: string[] = [];
|
|
265
332
|
const conflicts: PullConflict[] = [];
|
|
266
333
|
|
|
334
|
+
// Detect remote deletions: tracked locally but not in remote response
|
|
335
|
+
const remotePathSet = new Set(remoteDocs.map((d) => d.path));
|
|
336
|
+
for (const [localPath, tracked] of Object.entries(state.documents)) {
|
|
337
|
+
if (!remotePathSet.has(localPath)) {
|
|
338
|
+
// File was deleted on remote — check if locally modified
|
|
339
|
+
const absPath = resolve(root, localPath);
|
|
340
|
+
if (existsSync(absPath)) {
|
|
341
|
+
const localRaw = await readFile(absPath, 'utf-8');
|
|
342
|
+
const localHash = sha256(localRaw);
|
|
343
|
+
if (localHash !== tracked.localHash) {
|
|
344
|
+
// Locally modified but deleted on remote → conflict
|
|
345
|
+
conflicts.push({
|
|
346
|
+
path: localPath,
|
|
347
|
+
localVersion: tracked.remoteVersion.toString(),
|
|
348
|
+
remoteVersion: 0,
|
|
349
|
+
reason: 'File modified locally but deleted on remote',
|
|
350
|
+
});
|
|
351
|
+
} else {
|
|
352
|
+
// Not modified locally → safe to delete
|
|
353
|
+
await unlink(absPath);
|
|
354
|
+
deleted.push(localPath);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
delete state.documents[localPath];
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
267
361
|
for (const doc of remoteDocs) {
|
|
268
362
|
const localPath = doc.path;
|
|
269
363
|
const absPath = resolve(root, localPath);
|
|
@@ -304,9 +398,7 @@ export async function pullFromRemote(root: string, timeout?: number): Promise<Pu
|
|
|
304
398
|
});
|
|
305
399
|
} else {
|
|
306
400
|
// Only remote body changed — surgically replace body, preserve frontmatter
|
|
307
|
-
const
|
|
308
|
-
let updatedContent = replaceBody(localRaw, doc.content);
|
|
309
|
-
updatedContent = updatedContent.replace(/^status:\s*.+/m, `status: ${newStatus}`);
|
|
401
|
+
const updatedContent = replaceBody(localRaw, doc.content);
|
|
310
402
|
await writeFile(absPath, updatedContent, 'utf-8');
|
|
311
403
|
updated.push(localPath);
|
|
312
404
|
updateDocState(state, doc, localPath, updatedContent);
|
|
@@ -317,7 +409,7 @@ export async function pullFromRemote(root: string, timeout?: number): Promise<Pu
|
|
|
317
409
|
state.lastPull = new Date().toISOString();
|
|
318
410
|
await writeRemoteState(root, state);
|
|
319
411
|
|
|
320
|
-
return { created, updated, conflicts };
|
|
412
|
+
return { created, updated, deleted, conflicts };
|
|
321
413
|
}
|
|
322
414
|
|
|
323
415
|
function updateDocState(
|
|
@@ -352,6 +444,24 @@ export async function pullCRsFromRemote(root: string, timeout?: number): Promise
|
|
|
352
444
|
|
|
353
445
|
let created = 0;
|
|
354
446
|
let updated = 0;
|
|
447
|
+
let deletedCount = 0;
|
|
448
|
+
|
|
449
|
+
// Detect remote deletions: tracked locally but not in remote response
|
|
450
|
+
const remoteCRIdSet = new Set(remoteCRs.map((cr) => cr.id));
|
|
451
|
+
for (const [localPath, tracked] of Object.entries(state.changeRequests!)) {
|
|
452
|
+
if (!remoteCRIdSet.has(tracked.remoteId)) {
|
|
453
|
+
const absPath = resolve(root, localPath);
|
|
454
|
+
if (existsSync(absPath)) {
|
|
455
|
+
const localRaw = await readFile(absPath, 'utf-8');
|
|
456
|
+
const localHash = sha256(localRaw);
|
|
457
|
+
if (localHash === tracked.localHash) {
|
|
458
|
+
await unlink(absPath);
|
|
459
|
+
deletedCount++;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
delete state.changeRequests![localPath];
|
|
463
|
+
}
|
|
464
|
+
}
|
|
355
465
|
|
|
356
466
|
for (const cr of remoteCRs) {
|
|
357
467
|
const localPath = cr.path ?? idToPath.get(cr.id) ?? `change-requests/CR-${cr.id.substring(0, 8)}.md`;
|
|
@@ -378,9 +488,7 @@ export async function pullCRsFromRemote(root: string, timeout?: number): Promise
|
|
|
378
488
|
}
|
|
379
489
|
|
|
380
490
|
// Body changed — update body, preserve frontmatter
|
|
381
|
-
const
|
|
382
|
-
let updatedContent = replaceBody(localRaw, cr.body);
|
|
383
|
-
updatedContent = updatedContent.replace(/^status:\s*.+/m, `status: ${newStatus}`);
|
|
491
|
+
const updatedContent = replaceBody(localRaw, cr.body);
|
|
384
492
|
await writeFile(absPath, updatedContent, 'utf-8');
|
|
385
493
|
updated++;
|
|
386
494
|
|
|
@@ -405,7 +513,7 @@ export async function pullCRsFromRemote(root: string, timeout?: number): Promise
|
|
|
405
513
|
}
|
|
406
514
|
|
|
407
515
|
await writeRemoteState(root, state);
|
|
408
|
-
return { created, updated };
|
|
516
|
+
return { created, updated, deleted: deletedCount };
|
|
409
517
|
}
|
|
410
518
|
|
|
411
519
|
// ─── Pull Bugs ───────────────────────────────────────────────────────────
|
|
@@ -426,6 +534,24 @@ export async function pullBugsFromRemote(root: string, timeout?: number): Promis
|
|
|
426
534
|
|
|
427
535
|
let created = 0;
|
|
428
536
|
let updated = 0;
|
|
537
|
+
let deletedCount = 0;
|
|
538
|
+
|
|
539
|
+
// Detect remote deletions: tracked locally but not in remote response
|
|
540
|
+
const remoteBugIdSet = new Set(remoteBugs.map((b) => b.id));
|
|
541
|
+
for (const [localPath, tracked] of Object.entries(state.bugs!)) {
|
|
542
|
+
if (!remoteBugIdSet.has(tracked.remoteId)) {
|
|
543
|
+
const absPath = resolve(root, localPath);
|
|
544
|
+
if (existsSync(absPath)) {
|
|
545
|
+
const localRaw = await readFile(absPath, 'utf-8');
|
|
546
|
+
const localHash = sha256(localRaw);
|
|
547
|
+
if (localHash === tracked.localHash) {
|
|
548
|
+
await unlink(absPath);
|
|
549
|
+
deletedCount++;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
delete state.bugs![localPath];
|
|
553
|
+
}
|
|
554
|
+
}
|
|
429
555
|
|
|
430
556
|
for (const bug of remoteBugs) {
|
|
431
557
|
const localPath = bug.path ?? idToPath.get(bug.id) ?? `bugs/BUG-${bug.id.substring(0, 8)}.md`;
|
|
@@ -452,9 +578,7 @@ export async function pullBugsFromRemote(root: string, timeout?: number): Promis
|
|
|
452
578
|
}
|
|
453
579
|
|
|
454
580
|
// Body changed — update body, preserve frontmatter
|
|
455
|
-
const
|
|
456
|
-
let updatedContent = replaceBody(localRaw, bug.body);
|
|
457
|
-
updatedContent = updatedContent.replace(/^status:\s*.+/m, `status: ${newStatus}`);
|
|
581
|
+
const updatedContent = replaceBody(localRaw, bug.body);
|
|
458
582
|
await writeFile(absPath, updatedContent, 'utf-8');
|
|
459
583
|
updated++;
|
|
460
584
|
|
|
@@ -479,7 +603,7 @@ export async function pullBugsFromRemote(root: string, timeout?: number): Promis
|
|
|
479
603
|
}
|
|
480
604
|
|
|
481
605
|
await writeRemoteState(root, state);
|
|
482
|
-
return { created, updated };
|
|
606
|
+
return { created, updated, deleted: deletedCount };
|
|
483
607
|
}
|
|
484
608
|
|
|
485
609
|
// ─── Reset Remote ────────────────────────────────────────────────────────
|
|
@@ -490,8 +614,13 @@ export async function resetRemoteProject(root: string, confirmSlug: string, time
|
|
|
490
614
|
|
|
491
615
|
const result = await resetProject(api, confirmSlug);
|
|
492
616
|
|
|
493
|
-
// Clear local remote state
|
|
494
|
-
await writeRemoteState(root, {
|
|
617
|
+
// Clear local remote state and mark the project for a full reseed on the next push.
|
|
618
|
+
await writeRemoteState(root, {
|
|
619
|
+
needsReseed: true,
|
|
620
|
+
documents: {},
|
|
621
|
+
changeRequests: {},
|
|
622
|
+
bugs: {},
|
|
623
|
+
});
|
|
495
624
|
|
|
496
625
|
return result;
|
|
497
626
|
}
|
|
@@ -506,7 +635,14 @@ export async function getRemoteStatus(root: string, timeout?: number): Promise<R
|
|
|
506
635
|
}
|
|
507
636
|
|
|
508
637
|
const files = await parseAllStoryFiles(root);
|
|
509
|
-
const
|
|
638
|
+
const state = await readRemoteState(root);
|
|
639
|
+
const localPending = state.needsReseed
|
|
640
|
+
? files.length
|
|
641
|
+
: files.filter((f) => {
|
|
642
|
+
const tracked = state.documents[normalizePath(f.relativePath)];
|
|
643
|
+
if (!tracked) return true;
|
|
644
|
+
return tracked.localHash !== f.hash;
|
|
645
|
+
}).length;
|
|
510
646
|
|
|
511
647
|
try {
|
|
512
648
|
const api = buildApiConfig(config, timeout);
|
package/src/remote/types.ts
CHANGED
|
@@ -84,16 +84,24 @@ export interface RemoteEntityState {
|
|
|
84
84
|
export interface RemoteState {
|
|
85
85
|
lastPull?: string;
|
|
86
86
|
lastPush?: string;
|
|
87
|
+
needsReseed?: boolean;
|
|
87
88
|
documents: Record<string, RemoteDocState>;
|
|
88
89
|
changeRequests?: Record<string, RemoteEntityState>;
|
|
89
90
|
bugs?: Record<string, RemoteEntityState>;
|
|
90
91
|
}
|
|
91
92
|
|
|
93
|
+
/** Response from delete endpoints */
|
|
94
|
+
export interface RemoteDeleteResponse {
|
|
95
|
+
deleted: number;
|
|
96
|
+
paths: string[];
|
|
97
|
+
}
|
|
98
|
+
|
|
92
99
|
/** Result of a push operation */
|
|
93
100
|
export interface PushResult {
|
|
94
101
|
created: number;
|
|
95
102
|
updated: number;
|
|
96
103
|
pushed: string[];
|
|
104
|
+
deleted: string[];
|
|
97
105
|
}
|
|
98
106
|
|
|
99
107
|
/** A conflict detected during pull */
|
|
@@ -108,6 +116,7 @@ export interface PullConflict {
|
|
|
108
116
|
export interface PullResult {
|
|
109
117
|
created: string[];
|
|
110
118
|
updated: string[];
|
|
119
|
+
deleted: string[];
|
|
111
120
|
conflicts: PullConflict[];
|
|
112
121
|
}
|
|
113
122
|
|
|
@@ -115,6 +124,7 @@ export interface PullResult {
|
|
|
115
124
|
export interface PullEntitiesResult {
|
|
116
125
|
created: number;
|
|
117
126
|
updated: number;
|
|
127
|
+
deleted: number;
|
|
118
128
|
}
|
|
119
129
|
|
|
120
130
|
/** Result of remote status check */
|
package/src/sdd.ts
CHANGED
|
@@ -172,7 +172,7 @@ export class SDD {
|
|
|
172
172
|
const marked: string[] = [];
|
|
173
173
|
|
|
174
174
|
for (const bug of all) {
|
|
175
|
-
if (bug.frontmatter.status
|
|
175
|
+
if (bug.frontmatter.status !== "open") continue;
|
|
176
176
|
if (paths && paths.length > 0 && !paths.includes(bug.relativePath)) continue;
|
|
177
177
|
|
|
178
178
|
const absPath = resolve(this.root, bug.relativePath);
|
package/tests/apply.test.ts
CHANGED
|
@@ -59,40 +59,38 @@ describe('generateApplyPrompt', () => {
|
|
|
59
59
|
it('generates prompt with only bugs', () => {
|
|
60
60
|
const prompt = generateApplyPrompt([makeBug()], [], [], '/tmp');
|
|
61
61
|
expect(prompt).not.toBeNull();
|
|
62
|
-
expect(prompt).toContain('
|
|
63
|
-
expect(prompt).toContain('Open Bugs (1)');
|
|
62
|
+
expect(prompt).toContain('## Open bugs (1)');
|
|
64
63
|
expect(prompt).toContain('Login button broken');
|
|
65
64
|
expect(prompt).toContain('bugs/BUG-001.md');
|
|
66
|
-
expect(prompt).not.toContain('Pending
|
|
67
|
-
expect(prompt).not.toContain('Pending
|
|
65
|
+
expect(prompt).not.toContain('## Pending change requests');
|
|
66
|
+
expect(prompt).not.toContain('## Pending files');
|
|
68
67
|
});
|
|
69
68
|
|
|
70
69
|
it('generates prompt with only CRs', () => {
|
|
71
70
|
const prompt = generateApplyPrompt([], [makeCR()], [], '/tmp');
|
|
72
71
|
expect(prompt).not.toBeNull();
|
|
73
|
-
expect(prompt).toContain('Pending
|
|
72
|
+
expect(prompt).toContain('## Pending change requests (1)');
|
|
74
73
|
expect(prompt).toContain('Add dark mode');
|
|
75
|
-
expect(prompt).not.toContain('Open
|
|
76
|
-
expect(prompt).not.toContain('Pending
|
|
74
|
+
expect(prompt).not.toContain('## Open bugs');
|
|
75
|
+
expect(prompt).not.toContain('## Pending files');
|
|
77
76
|
});
|
|
78
77
|
|
|
79
78
|
it('generates prompt with only pending files', () => {
|
|
80
79
|
const prompt = generateApplyPrompt([], [], [makeFile()], '/tmp');
|
|
81
80
|
expect(prompt).not.toBeNull();
|
|
82
|
-
expect(prompt).toContain('Pending
|
|
81
|
+
expect(prompt).toContain('## Pending files (1)');
|
|
83
82
|
expect(prompt).toContain('product/features/auth.md');
|
|
84
83
|
expect(prompt).toContain('**new**');
|
|
85
|
-
expect(prompt).not.toContain('Open
|
|
86
|
-
expect(prompt).not.toContain('Pending
|
|
84
|
+
expect(prompt).not.toContain('## Open bugs');
|
|
85
|
+
expect(prompt).not.toContain('## Pending change requests');
|
|
87
86
|
});
|
|
88
87
|
|
|
89
88
|
it('generates prompt with all three', () => {
|
|
90
89
|
const prompt = generateApplyPrompt([makeBug()], [makeCR()], [makeFile()], '/tmp');
|
|
91
90
|
expect(prompt).not.toBeNull();
|
|
92
|
-
expect(prompt).toContain('Open
|
|
93
|
-
expect(prompt).toContain('Pending
|
|
94
|
-
expect(prompt).toContain('Pending
|
|
95
|
-
expect(prompt).toContain('## Instructions');
|
|
91
|
+
expect(prompt).toContain('## Open bugs (1)');
|
|
92
|
+
expect(prompt).toContain('## Pending change requests (1)');
|
|
93
|
+
expect(prompt).toContain('## Pending files (1)');
|
|
96
94
|
});
|
|
97
95
|
});
|
|
98
96
|
|
package/tests/bug.test.ts
CHANGED
|
@@ -36,6 +36,18 @@ created-at: "2025-01-02T00:00:00.000Z"
|
|
|
36
36
|
The navigation bar is misaligned on mobile devices.
|
|
37
37
|
`;
|
|
38
38
|
|
|
39
|
+
const BUG_DRAFT = `---
|
|
40
|
+
title: "Incomplete bug draft"
|
|
41
|
+
status: draft
|
|
42
|
+
author: "user"
|
|
43
|
+
created-at: "2025-01-03T00:00:00.000Z"
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Description
|
|
47
|
+
|
|
48
|
+
Draft bug details to enrich.
|
|
49
|
+
`;
|
|
50
|
+
|
|
39
51
|
describe('Bug parser', () => {
|
|
40
52
|
it('parses bug frontmatter correctly', () => {
|
|
41
53
|
const result = parseBugFile('bugs/BUG-001.md', BUG_OPEN);
|
|
@@ -147,6 +159,16 @@ describe('SDD Bug methods', () => {
|
|
|
147
159
|
expect(marked).toHaveLength(0);
|
|
148
160
|
});
|
|
149
161
|
|
|
162
|
+
it('markBugResolved() skips draft bugs', async () => {
|
|
163
|
+
await writeFile(join(tempDir, 'bugs/BUG-001.md'), BUG_DRAFT, 'utf-8');
|
|
164
|
+
|
|
165
|
+
const marked = await sdd.markBugResolved();
|
|
166
|
+
expect(marked).toHaveLength(0);
|
|
167
|
+
|
|
168
|
+
const content = await readFile(join(tempDir, 'bugs/BUG-001.md'), 'utf-8');
|
|
169
|
+
expect(content).toContain('status: draft');
|
|
170
|
+
});
|
|
171
|
+
|
|
150
172
|
it('integration: create bug → open → mark resolved → no longer open', async () => {
|
|
151
173
|
await writeFile(join(tempDir, 'bugs/BUG-001.md'), BUG_OPEN, 'utf-8');
|
|
152
174
|
|
|
@@ -93,7 +93,7 @@ describe("SDD integration", () => {
|
|
|
93
93
|
|
|
94
94
|
// Sync — prompt should list the new file
|
|
95
95
|
const prompt = await sdd.sync();
|
|
96
|
-
expect(prompt).toContain("
|
|
96
|
+
expect(prompt).toContain("## Pending files (1)");
|
|
97
97
|
expect(prompt).toContain("product/vision.md");
|
|
98
98
|
expect(prompt).toContain("**new**");
|
|
99
99
|
|
package/tests/prompt.test.ts
CHANGED
|
@@ -25,10 +25,9 @@ describe('generatePrompt', () => {
|
|
|
25
25
|
it('generates prompt with new files', () => {
|
|
26
26
|
const files = [makeFile()];
|
|
27
27
|
const prompt = generatePrompt(files, '/tmp');
|
|
28
|
-
expect(prompt).toContain('
|
|
28
|
+
expect(prompt).toContain('## Pending files (1)');
|
|
29
29
|
expect(prompt).toContain('product/features/auth.md');
|
|
30
30
|
expect(prompt).toContain('**new**');
|
|
31
|
-
expect(prompt).toContain('Read each file listed above');
|
|
32
31
|
});
|
|
33
32
|
|
|
34
33
|
it('generates prompt with deleted files', () => {
|
|
@@ -38,7 +37,7 @@ describe('generatePrompt', () => {
|
|
|
38
37
|
const prompt = generatePrompt(files, '/tmp');
|
|
39
38
|
expect(prompt).toContain('**deleted**');
|
|
40
39
|
expect(prompt).toContain('Files to remove');
|
|
41
|
-
expect(prompt).toContain('
|
|
40
|
+
expect(prompt).toContain('product/features/auth.md');
|
|
42
41
|
});
|
|
43
42
|
|
|
44
43
|
it('generates prompt with changed files', () => {
|
|
@@ -51,7 +50,6 @@ describe('generatePrompt', () => {
|
|
|
51
50
|
|
|
52
51
|
it('generates empty prompt when no pending files', () => {
|
|
53
52
|
const prompt = generatePrompt([]);
|
|
54
|
-
expect(prompt).
|
|
55
|
-
expect(prompt).toContain('Nothing to do');
|
|
53
|
+
expect(prompt).toBe('Nothing to do — all files are synced.');
|
|
56
54
|
});
|
|
57
55
|
});
|
|
@@ -20,7 +20,7 @@ describe('Remote state manager', () => {
|
|
|
20
20
|
|
|
21
21
|
it('returns empty state when file does not exist', async () => {
|
|
22
22
|
const state = await readRemoteState(tempDir);
|
|
23
|
-
expect(state).toEqual({ documents: {} });
|
|
23
|
+
expect(state).toEqual({ documents: {}, changeRequests: {}, bugs: {} });
|
|
24
24
|
});
|
|
25
25
|
|
|
26
26
|
it('round-trips state correctly', async () => {
|
|
@@ -57,7 +57,7 @@ describe('Remote state manager', () => {
|
|
|
57
57
|
await writeFile(join(tempDir, '.sdd', 'remote-state.json'), 'not valid json', 'utf-8');
|
|
58
58
|
|
|
59
59
|
const state = await readRemoteState(tempDir);
|
|
60
|
-
expect(state).toEqual({ documents: {} });
|
|
60
|
+
expect(state).toEqual({ documents: {}, changeRequests: {}, bugs: {} });
|
|
61
61
|
});
|
|
62
62
|
|
|
63
63
|
it('updates individual document entries', async () => {
|