lean-spec 0.2.9 → 0.2.10
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 +1 -1
- package/dist/backfill-446GBTBC.js +5 -0
- package/dist/{backfill-WYW47B42.js.map → backfill-446GBTBC.js.map} +1 -1
- package/dist/{chunk-FILG4YF3.js → chunk-CJMVV46H.js} +3 -3
- package/dist/{chunk-FILG4YF3.js.map → chunk-CJMVV46H.js.map} +1 -1
- package/dist/{chunk-SZNMHOHN.js → chunk-H5MCUMBK.js} +312 -15
- package/dist/chunk-H5MCUMBK.js.map +1 -0
- package/dist/{chunk-JX4PXHNC.js → chunk-KTNU4LUR.js} +1106 -837
- package/dist/chunk-KTNU4LUR.js.map +1 -0
- package/dist/{chunk-K4VTB6BF.js → chunk-RF5PKL6L.js} +3 -3
- package/dist/{chunk-K4VTB6BF.js.map → chunk-RF5PKL6L.js.map} +1 -1
- package/dist/{chunk-ENX5NYTE.js → chunk-VN5BUHTV.js} +5 -2
- package/dist/chunk-VN5BUHTV.js.map +1 -0
- package/dist/cli.js +5 -5
- package/dist/{frontmatter-Q5UJOZLR.js → frontmatter-6ZBAGOEU.js} +3 -3
- package/dist/{frontmatter-Q5UJOZLR.js.map → frontmatter-6ZBAGOEU.js.map} +1 -1
- package/dist/mcp-server.js +5 -5
- package/dist/validate-DIWYTDEF.js +5 -0
- package/dist/{validate-LMCR5JTN.js.map → validate-DIWYTDEF.js.map} +1 -1
- package/package.json +2 -2
- package/templates/detailed/AGENTS.md +55 -101
- package/templates/standard/AGENTS.md +55 -101
- package/dist/backfill-WYW47B42.js +0 -5
- package/dist/chunk-ENX5NYTE.js.map +0 -1
- package/dist/chunk-JX4PXHNC.js.map +0 -1
- package/dist/chunk-SZNMHOHN.js.map +0 -1
- package/dist/validate-LMCR5JTN.js +0 -5
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getSpecFile, parseFrontmatter, matchesFilter } from './chunk-
|
|
1
|
+
import { getSpecFile, parseFrontmatter, matchesFilter } from './chunk-VN5BUHTV.js';
|
|
2
2
|
import * as fs2 from 'fs/promises';
|
|
3
3
|
import * as path2 from 'path';
|
|
4
4
|
|
|
@@ -294,5 +294,5 @@ async function getSpec(specPath) {
|
|
|
294
294
|
}
|
|
295
295
|
|
|
296
296
|
export { extractGroup, getSpec, loadAllSpecs, loadConfig, loadSubFiles, resolvePrefix, saveConfig };
|
|
297
|
-
//# sourceMappingURL=chunk-
|
|
298
|
-
//# sourceMappingURL=chunk-
|
|
297
|
+
//# sourceMappingURL=chunk-RF5PKL6L.js.map
|
|
298
|
+
//# sourceMappingURL=chunk-RF5PKL6L.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/config.ts","../src/spec-loader.ts"],"names":["path","fs","stat","aDate","bDate"],"mappings":";;;;AAiCA,IAAM,cAAA,GAAiC;AAAA,EACrC,QAAA,EAAU,kBAAA;AAAA,EACV,SAAA,EAAW;AAAA,IACT,OAAA,EAAS;AAAA,GACX;AAAA,EACA,QAAA,EAAU,OAAA;AAAA,EACV,SAAA,EAAW;AAAA,IACT,OAAA,EAAS,MAAA;AAAA;AAAA,IACT,MAAA,EAAQ,EAAA;AAAA;AAAA,IACR,UAAA,EAAY,UAAA;AAAA,IACZ,cAAA,EAAgB,CAAA;AAAA,IAChB,WAAA,EAAa;AAAA,GACf;AAAA,EACA,QAAA,EAAU;AAAA,IACR,QAAA,EAAU,IAAA;AAAA,IACV,QAAA,EAAU;AAAA;AAEd,CAAA;AAEA,eAAsB,UAAA,CAAW,GAAA,GAAc,OAAA,CAAQ,GAAA,EAAI,EAA4B;AACrF,EAAA,MAAM,UAAA,GAAkBA,KAAA,CAAA,IAAA,CAAK,GAAA,EAAK,YAAA,EAAc,aAAa,CAAA;AAE7D,EAAA,IAAI;AACF,IAAA,MAAM,OAAA,GAAU,MAASC,GAAA,CAAA,QAAA,CAAS,UAAA,EAAY,OAAO,CAAA;AACrD,IAAA,MAAM,UAAA,GAAa,IAAA,CAAK,KAAA,CAAM,OAAO,CAAA;AACrC,IAAA,MAAM,MAAA,GAAS,EAAE,GAAG,cAAA,EAAgB,GAAG,UAAA,EAAW;AAGlD,IAAA,sBAAA,CAAuB,MAAM,CAAA;AAE7B,IAAA,OAAO,MAAA;AAAA,EACT,CAAA,CAAA,MAAQ;AAEN,IAAA,OAAO,cAAA;AAAA,EACT;AACF;AAEA,eAAsB,UAAA,CACpB,MAAA,EACA,GAAA,GAAc,OAAA,CAAQ,KAAI,EACX;AACf,EAAA,MAAM,SAAA,GAAiBD,KAAA,CAAA,IAAA,CAAK,GAAA,EAAK,YAAY,CAAA;AAC7C,EAAA,MAAM,UAAA,GAAkBA,KAAA,CAAA,IAAA,CAAK,SAAA,EAAW,aAAa,CAAA;AAErD,EAAA,MAASC,GAAA,CAAA,KAAA,CAAM,SAAA,EAAW,EAAE,SAAA,EAAW,MAAM,CAAA;AAC7C,EAAA,MAASA,GAAA,CAAA,SAAA,CAAU,YAAY,IAAA,CAAK,SAAA,CAAU,QAAQ,IAAA,EAAM,CAAC,GAAG,OAAO,CAAA;AACzE;AAEO,SAAS,QAAA,CAAS,SAAiB,UAAA,EAAoB;AAC5D,EAAA,MAAM,GAAA,uBAAU,IAAA,EAAK;AACrB,EAAA,MAAM,IAAA,GAAO,IAAI,WAAA,EAAY;AAC7B,EAAA,MAAM,KAAA,GAAQ,OAAO,GAAA,CAAI,QAAA,KAAa,CAAC,CAAA,CAAE,QAAA,CAAS,CAAA,EAAG,GAAG,CAAA;AACxD,EAAA,MAAM,GAAA,GAAM,OAAO,GAAA,CAAI,OAAA,EAAS,CAAA,CAAE,QAAA,CAAS,GAAG,GAAG,CAAA;AAEjD,EAAA,QAAQ,MAAA;AAAQ,IACd,KAAK,UAAA;AACH,MAAA,OAAO,CAAA,EAAG,IAAI,CAAA,EAAG,KAAK,GAAG,GAAG,CAAA,CAAA;AAAA,IAC9B,KAAK,YAAA;AACH,MAAA,OAAO,CAAA,EAAG,IAAI,CAAA,CAAA,EAAI,KAAK,IAAI,GAAG,CAAA,CAAA;AAAA,IAChC,KAAK,SAAA;AACH,MAAA,OAAO,CAAA,EAAG,IAAI,CAAA,CAAA,EAAI,KAAK,CAAA,CAAA;AAAA,IACzB,KAAK,SAAA;AACH,MAAA,OAAO,CAAA,EAAG,IAAI,CAAA,CAAA,EAAI,KAAK,CAAA,CAAA;AAAA,IACzB,KAAK,MAAA;AACH,MAAA,OAAO,OAAO,IAAI,CAAA;AAAA,IACpB,KAAK,IAAA;AACH,MAAA,OAAO,KAAA;AAAA,IACT,KAAK,IAAA;AACH,MAAA,OAAO,GAAA;AAAA,IACT;AACE,MAAA,OAAO,CAAA,EAAG,IAAI,CAAA,EAAG,KAAK,GAAG,GAAG,CAAA,CAAA;AAAA;AAElC;AAKO,SAAS,uBAAuB,MAAA,EAA8B;AACnE,EAAA,MAAM,OAAA,GAAU,OAAO,SAAA,CAAU,OAAA;AAGjC,EAAA,IAAI,OAAA,IAAW,OAAA,CAAQ,QAAA,CAAS,QAAQ,CAAA,IAAK,OAAA,CAAQ,QAAA,CAAS,OAAO,CAAA,IAAK,OAAA,CAAQ,QAAA,CAAS,QAAQ,CAAA,EAAG;AACpG,IAAA,MAAA,CAAO,UAAU,OAAA,GAAU,QAAA;AAC3B,IAAA,MAAA,CAAO,SAAA,CAAU,cAAA,GAAiB,CAAA,CAAA,EAAI,MAAA,CAAO,UAAU,UAAU,CAAA,CAAA,CAAA;AAAA,EACnE;AACF;AAKO,SAAS,aAAA,CACd,MAAA,EACA,UAAA,GAAqB,UAAA,EACb;AACR,EAAA,MAAM,gBAAA,GAAiD;AAAA,IACrD,YAAA,EAAc,MAAM,QAAA,CAAS,UAAU,CAAA;AAAA,IACvC,cAAA,EAAgB,MAAM,QAAA,CAAS,YAAY,CAAA;AAAA,IAC3C,WAAA,EAAa,MAAM,QAAA,CAAS,SAAS,CAAA;AAAA,IACrC,QAAA,EAAU,MAAM,QAAA,CAAS,MAAM,CAAA;AAAA,IAC/B,MAAA,EAAQ,MAAM,QAAA,CAAS,IAAI,CAAA;AAAA,IAC3B,MAAA,EAAQ,MAAM,QAAA,CAAS,IAAI;AAAA,GAC7B;AAEA,EAAA,IAAI,MAAA,GAAS,MAAA;AACb,EAAA,KAAA,MAAW,CAAC,OAAA,EAAS,EAAE,KAAK,MAAA,CAAO,OAAA,CAAQ,gBAAgB,CAAA,EAAG;AAC5D,IAAA,MAAA,GAAS,MAAA,CAAO,OAAA,CAAQ,OAAA,EAAS,EAAA,EAAI,CAAA;AAAA,EACvC;AAEA,EAAA,OAAO,MAAA;AACT;AAKO,SAAS,YAAA,CACd,SAAA,EACA,UAAA,GAAqB,UAAA,EACrB,QACA,QAAA,EACQ;AACR,EAAA,MAAM,gBAAA,GAAiD;AAAA,IACrD,YAAA,EAAc,MAAM,QAAA,CAAS,UAAU,CAAA;AAAA,IACvC,cAAA,EAAgB,MAAM,QAAA,CAAS,YAAY,CAAA;AAAA,IAC3C,WAAA,EAAa,MAAM,QAAA,CAAS,SAAS,CAAA;AAAA,IACrC,QAAA,EAAU,MAAM,QAAA,CAAS,MAAM,CAAA;AAAA,IAC/B,MAAA,EAAQ,MAAM,QAAA,CAAS,IAAI,CAAA;AAAA,IAC3B,MAAA,EAAQ,MAAM,QAAA,CAAS,IAAI;AAAA,GAC7B;AAEA,EAAA,IAAI,MAAA,GAAS,SAAA;AAGb,EAAA,KAAA,MAAW,CAAC,OAAA,EAAS,EAAE,KAAK,MAAA,CAAO,OAAA,CAAQ,gBAAgB,CAAA,EAAG;AAC5D,IAAA,MAAA,GAAS,MAAA,CAAO,OAAA,CAAQ,OAAA,EAAS,EAAA,EAAI,CAAA;AAAA,EACvC;AAGA,EAAA,MAAM,YAAA,GAAe,MAAA,CAAO,KAAA,CAAM,cAAc,CAAA;AAChD,EAAA,IAAI,YAAA,EAAc;AAChB,IAAA,KAAA,MAAW,SAAS,YAAA,EAAc;AAChC,MAAA,MAAM,SAAA,GAAY,KAAA,CAAM,KAAA,CAAM,CAAA,EAAG,EAAE,CAAA;AACnC,MAAA,MAAM,UAAA,GAAa,SAAS,SAAS,CAAA;AAErC,MAAA,IAAI,eAAe,MAAA,EAAW;AAC5B,QAAA,IAAI,CAAC,QAAA,EAAU;AACb,UAAA,MAAM,IAAI,KAAA,CAAM,CAAA,cAAA,EAAiB,SAAS,CAAA,sFAAA,EAAyF,SAAS,CAAA,QAAA,CAAU,CAAA;AAAA,QACxJ;AACA,QAAA,OAAO,QAAA;AAAA,MACT;AAEA,MAAA,MAAA,GAAS,MAAA,CAAO,OAAA,CAAQ,KAAA,EAAO,MAAA,CAAO,UAAU,CAAC,CAAA;AAAA,IACnD;AAAA,EACF;AAEA,EAAA,OAAO,MAAA;AACT;ACnKA,eAAsB,YAAA,CACpB,OAAA,EACA,OAAA,GAAwC,EAAC,EACjB;AACxB,EAAA,MAAM,WAA0B,EAAC;AAEjC,EAAA,IAAI;AACF,IAAA,MAAM,UAAU,MAAS,GAAA,CAAA,OAAA,CAAQ,SAAS,EAAE,aAAA,EAAe,MAAM,CAAA;AAEjE,IAAA,KAAA,MAAW,SAAS,OAAA,EAAS;AAE3B,MAAA,IAAI,KAAA,CAAM,SAAS,WAAA,EAAa;AAGhC,MAAA,IAAI,KAAA,CAAM,aAAY,EAAG;AAEzB,MAAA,MAAM,QAAA,GAAgB,KAAA,CAAA,IAAA,CAAK,OAAA,EAAS,KAAA,CAAM,IAAI,CAAA;AAC9C,MAAA,MAAMC,KAAAA,GAAO,MAAS,GAAA,CAAA,IAAA,CAAK,QAAQ,CAAA;AAGnC,MAAA,MAAM,GAAA,GAAW,KAAA,CAAA,OAAA,CAAQ,KAAA,CAAM,IAAI,EAAE,WAAA,EAAY;AACjD,MAAA,MAAM,aAAa,GAAA,KAAQ,KAAA;AAE3B,MAAA,MAAM,OAAA,GAAuB;AAAA,QAC3B,MAAM,KAAA,CAAM,IAAA;AAAA,QACZ,IAAA,EAAM,QAAA;AAAA,QACN,MAAMA,KAAAA,CAAK,IAAA;AAAA,QACX,IAAA,EAAM,aAAa,UAAA,GAAa;AAAA,OAClC;AAGA,MAAA,IAAI,UAAA,IAAc,QAAQ,cAAA,EAAgB;AACxC,QAAA,OAAA,CAAQ,OAAA,GAAU,MAAS,GAAA,CAAA,QAAA,CAAS,QAAA,EAAU,OAAO,CAAA;AAAA,MACvD;AAEA,MAAA,QAAA,CAAS,KAAK,OAAO,CAAA;AAAA,IACvB;AAAA,EACF,SAAS,KAAA,EAAO;AAGd,IAAA,OAAO,EAAC;AAAA,EACV;AAGA,EAAA,OAAO,QAAA,CAAS,IAAA,CAAK,CAAC,CAAA,EAAG,CAAA,KAAM;AAC7B,IAAA,IAAI,CAAA,CAAE,IAAA,KAAS,CAAA,CAAE,IAAA,EAAM;AACrB,MAAA,OAAO,CAAA,CAAE,IAAA,KAAS,UAAA,GAAa,EAAA,GAAK,CAAA;AAAA,IACtC;AACA,IAAA,OAAO,CAAA,CAAE,IAAA,CAAK,aAAA,CAAc,CAAA,CAAE,IAAI,CAAA;AAAA,EACpC,CAAC,CAAA;AACH;AAGA,eAAsB,YAAA,CAAa,OAAA,GAO/B,EAAC,EAAwB;AAC3B,EAAA,MAAM,MAAA,GAAS,MAAM,UAAA,EAAW;AAChC,EAAA,MAAM,GAAA,GAAM,QAAQ,GAAA,EAAI;AACxB,EAAA,MAAM,QAAA,GAAgB,KAAA,CAAA,IAAA,CAAK,GAAA,EAAK,MAAA,CAAO,QAAQ,CAAA;AAE/C,EAAA,MAAM,QAAoB,EAAC;AAG3B,EAAA,IAAI;AACF,IAAA,MAAS,WAAO,QAAQ,CAAA;AAAA,EAC1B,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,EAAC;AAAA,EACV;AAGA,EAAA,MAAM,WAAA,GAAc,YAAA;AAGpB,EAAA,eAAe,gBAAA,CAAiB,GAAA,EAAa,YAAA,GAAuB,EAAA,EAAmB;AACrF,IAAA,IAAI;AACF,MAAA,MAAM,UAAU,MAAS,GAAA,CAAA,OAAA,CAAQ,KAAK,EAAE,aAAA,EAAe,MAAM,CAAA;AAE7D,MAAA,KAAA,MAAW,SAAS,OAAA,EAAS;AAC3B,QAAA,IAAI,CAAC,KAAA,CAAM,WAAA,EAAY,EAAG;AAG1B,QAAA,IAAI,KAAA,CAAM,IAAA,KAAS,UAAA,IAAc,YAAA,KAAiB,EAAA,EAAI;AAEtD,QAAA,MAAM,SAAA,GAAiB,KAAA,CAAA,IAAA,CAAK,GAAA,EAAK,KAAA,CAAM,IAAI,CAAA;AAC3C,QAAA,MAAM,iBAAA,GAAoB,eAAe,CAAA,EAAG,YAAY,IAAI,KAAA,CAAM,IAAI,KAAK,KAAA,CAAM,IAAA;AAGjF,QAAA,IAAI,WAAA,CAAY,IAAA,CAAK,KAAA,CAAM,IAAI,CAAA,EAAG;AAChC,UAAA,MAAM,WAAW,MAAM,WAAA,CAAY,SAAA,EAAW,MAAA,CAAO,UAAU,WAAW,CAAA;AAE1E,UAAA,IAAI,QAAA,EAAU;AACZ,YAAA,MAAM,WAAA,GAAc,MAAM,gBAAA,CAAiB,QAAA,EAAU,MAAM,CAAA;AAE3D,YAAA,IAAI,WAAA,EAAa;AAEf,cAAA,IAAI,QAAQ,MAAA,IAAU,CAAC,cAAc,WAAA,EAAa,OAAA,CAAQ,MAAM,CAAA,EAAG;AACjE,gBAAA;AAAA,cACF;AAGA,cAAA,MAAM,SAAA,GAAY,iBAAA,CAAkB,KAAA,CAAM,SAAS,CAAA;AACnD,cAAA,IAAI,IAAA;AAEJ,cAAA,IAAI,SAAA,EAAW;AACb,gBAAA,IAAA,GAAO,UAAU,CAAC,CAAA;AAAA,cACpB,CAAA,MAAA,IAAW,OAAO,WAAA,CAAY,OAAA,KAAY,QAAA,EAAU;AAClD,gBAAA,IAAA,GAAO,WAAA,CAAY,OAAA;AAAA,cACrB,CAAA,MAAA,IAAW,YAAY,OAAA,EAAS;AAC9B,gBAAA,IAAA,GAAO,MAAA,CAAO,YAAY,OAAO,CAAA;AAAA,cACnC,CAAA,MAAO;AACL,gBAAA,IAAA,GAAO,EAAA;AAAA,cACT;AAEA,cAAA,MAAM,QAAA,GAAqB;AAAA,gBACzB,IAAA,EAAM,iBAAA;AAAA,gBACN,QAAA,EAAU,SAAA;AAAA,gBACV,QAAA,EAAU,QAAA;AAAA,gBACV,MAAM,KAAA,CAAM,IAAA;AAAA,gBACZ,IAAA;AAAA,gBACA;AAAA,eACF;AAGA,cAAA,IAAI,QAAQ,cAAA,EAAgB;AAC1B,gBAAA,QAAA,CAAS,OAAA,GAAU,MAAS,GAAA,CAAA,QAAA,CAAS,QAAA,EAAU,OAAO,CAAA;AAAA,cACxD;AAGA,cAAA,IAAI,QAAQ,eAAA,EAAiB;AAC3B,gBAAA,QAAA,CAAS,QAAA,GAAW,MAAM,YAAA,CAAa,SAAA,EAAW;AAAA,kBAChD,gBAAgB,OAAA,CAAQ;AAAA,iBACzB,CAAA;AAAA,cACH;AAEA,cAAA,KAAA,CAAM,KAAK,QAAQ,CAAA;AAAA,YACrB;AAAA,UACF;AAAA,QACF,CAAA,MAAO;AAEL,UAAA,MAAM,gBAAA,CAAiB,WAAW,iBAAiB,CAAA;AAAA,QACrD;AAAA,MACF;AAAA,IACF,CAAA,CAAA,MAAQ;AAAA,IAER;AAAA,EACF;AAGA,EAAA,MAAM,iBAAiB,QAAQ,CAAA;AAG/B,EAAA,IAAI,QAAQ,eAAA,EAAiB;AAC3B,IAAA,MAAM,YAAA,GAAoB,KAAA,CAAA,IAAA,CAAK,QAAA,EAAU,UAAU,CAAA;AACnD,IAAA,MAAM,gBAAA,CAAiB,cAAc,UAAU,CAAA;AAAA,EACjD;AAGA,EAAA,MAAM,MAAA,GAAS,QAAQ,MAAA,IAAU,IAAA;AACjC,EAAA,MAAM,SAAA,GAAY,QAAQ,SAAA,IAAa,MAAA;AAEvC,EAAA,KAAA,CAAM,IAAA,CAAK,CAAC,CAAA,EAAG,CAAA,KAAM;AACnB,IAAA,IAAI,UAAA,GAAa,CAAA;AAEjB,IAAA,QAAQ,MAAA;AAAQ,MACd,KAAK,IAAA;AAAA,MACL,KAAK,QAAA,EAAU;AAEb,QAAA,MAAM,IAAA,GAAO,QAAA,CAAS,CAAA,CAAE,IAAA,CAAK,KAAA,CAAM,QAAQ,CAAA,GAAI,CAAC,CAAA,IAAK,GAAA,EAAK,EAAE,CAAA;AAC5D,QAAA,MAAM,IAAA,GAAO,QAAA,CAAS,CAAA,CAAE,IAAA,CAAK,KAAA,CAAM,QAAQ,CAAA,GAAI,CAAC,CAAA,IAAK,GAAA,EAAK,EAAE,CAAA;AAC5D,QAAA,UAAA,GAAa,IAAA,GAAO,IAAA;AACpB,QAAA;AAAA,MACF;AAAA,MACA,KAAK,SAAA,EAAW;AAEd,QAAA,MAAMC,MAAAA,GAAQ,MAAA,CAAO,CAAA,CAAE,WAAA,CAAY,WAAW,EAAE,CAAA;AAChD,QAAA,MAAMC,MAAAA,GAAQ,MAAA,CAAO,CAAA,CAAE,WAAA,CAAY,WAAW,EAAE,CAAA;AAChD,QAAA,UAAA,GAAaD,MAAAA,CAAM,cAAcC,MAAK,CAAA;AACtC,QAAA;AAAA,MACF;AAAA,MACA,KAAK,MAAA,EAAQ;AACX,QAAA,UAAA,GAAa,CAAA,CAAE,IAAA,CAAK,aAAA,CAAc,CAAA,CAAE,IAAI,CAAA;AACxC,QAAA;AAAA,MACF;AAAA,MACA,KAAK,QAAA,EAAU;AACb,QAAA,UAAA,GAAa,EAAE,WAAA,CAAY,MAAA,CAAO,aAAA,CAAc,CAAA,CAAE,YAAY,MAAM,CAAA;AACpE,QAAA;AAAA,MACF;AAAA,MACA,KAAK,UAAA,EAAY;AAEf,QAAA,MAAM,aAAA,GAAgB,EAAE,QAAA,EAAU,CAAA,EAAG,MAAM,CAAA,EAAG,MAAA,EAAQ,CAAA,EAAG,GAAA,EAAK,CAAA,EAAE;AAChE,QAAA,MAAM,SAAA,GAAY,EAAE,WAAA,CAAY,QAAA,GAAW,cAAc,CAAA,CAAE,WAAA,CAAY,QAAQ,CAAA,GAAI,CAAA;AACnF,QAAA,MAAM,SAAA,GAAY,EAAE,WAAA,CAAY,QAAA,GAAW,cAAc,CAAA,CAAE,WAAA,CAAY,QAAQ,CAAA,GAAI,CAAA;AACnF,QAAA,UAAA,GAAa,SAAA,GAAY,SAAA;AACzB,QAAA;AAAA,MACF;AAAA,MACA;AAEE,QAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,CAAA,CAAE,WAAA,CAAY,WAAW,EAAE,CAAA;AAChD,QAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,CAAA,CAAE,WAAA,CAAY,WAAW,EAAE,CAAA;AAChD,QAAA,UAAA,GAAa,KAAA,CAAM,cAAc,KAAK,CAAA;AAAA;AAI1C,IAAA,OAAO,SAAA,KAAc,MAAA,GAAS,CAAC,UAAA,GAAa,UAAA;AAAA,EAC9C,CAAC,CAAA;AAED,EAAA,OAAO,KAAA;AACT;AAGA,eAAsB,QAAQ,QAAA,EAA4C;AACxE,EAAA,MAAM,MAAA,GAAS,MAAM,UAAA,EAAW;AAChC,EAAA,MAAM,GAAA,GAAM,QAAQ,GAAA,EAAI;AACxB,EAAA,MAAM,QAAA,GAAgB,KAAA,CAAA,IAAA,CAAK,GAAA,EAAK,MAAA,CAAO,QAAQ,CAAA;AAG/C,EAAA,IAAI,QAAA;AACJ,EAAA,IAAS,KAAA,CAAA,UAAA,CAAW,QAAQ,CAAA,EAAG;AAC7B,IAAA,QAAA,GAAW,QAAA;AAAA,EACb,CAAA,MAAO;AACL,IAAA,QAAA,GAAgB,KAAA,CAAA,IAAA,CAAK,UAAU,QAAQ,CAAA;AAAA,EACzC;AAGA,EAAA,IAAI;AACF,IAAA,MAAS,WAAO,QAAQ,CAAA;AAAA,EAC1B,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,MAAM,WAAW,MAAM,WAAA,CAAY,QAAA,EAAU,MAAA,CAAO,UAAU,WAAW,CAAA;AACzE,EAAA,IAAI,CAAC,UAAU,OAAO,IAAA;AAEtB,EAAA,MAAM,WAAA,GAAc,MAAM,gBAAA,CAAiB,QAAA,EAAU,MAAM,CAAA;AAC3D,EAAA,IAAI,CAAC,aAAa,OAAO,IAAA;AAEzB,EAAA,MAAM,OAAA,GAAU,MAAS,GAAA,CAAA,QAAA,CAAS,QAAA,EAAU,OAAO,CAAA;AAGnD,EAAA,MAAM,YAAA,GAAoB,KAAA,CAAA,QAAA,CAAS,QAAA,EAAU,QAAQ,CAAA;AACrD,EAAA,MAAM,KAAA,GAAQ,YAAA,CAAa,KAAA,CAAW,KAAA,CAAA,GAAG,CAAA;AACzC,EAAA,MAAM,IAAA,GAAO,MAAM,CAAC,CAAA,KAAM,aAAa,KAAA,CAAM,CAAC,CAAA,GAAI,KAAA,CAAM,CAAC,CAAA;AACzD,EAAA,MAAM,IAAA,GAAO,KAAA,CAAM,KAAA,CAAM,MAAA,GAAS,CAAC,CAAA;AAEnC,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,YAAA;AAAA,IACN,QAAA;AAAA,IACA,QAAA,EAAU,QAAA;AAAA,IACV,IAAA;AAAA,IACA,IAAA;AAAA,IACA,WAAA;AAAA,IACA;AAAA,GACF;AACF","file":"chunk-K4VTB6BF.js","sourcesContent":["import * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\n\nexport interface LeanSpecConfig {\n template: string;\n templates?: Record<string, string>; // Maps template name to filename\n specsDir: string;\n autoCheck?: boolean; // Enable/disable auto-check for sequence conflicts (default: true)\n structure: {\n pattern: 'flat' | 'custom' | string; // 'flat' or 'custom', or legacy pattern string\n dateFormat: string;\n sequenceDigits: number;\n defaultFile: string;\n prefix?: string; // For flat pattern: \"{YYYYMMDD}-\" or \"spec-\" (optional, default: empty for global numbering)\n groupExtractor?: string; // For custom pattern: \"{YYYYMMDD}\" or \"milestone-{milestone}\"\n groupFallback?: string; // Fallback folder if field missing (only for non-date extractors)\n };\n features?: {\n aiAgents?: boolean;\n examples?: boolean;\n collaboration?: boolean;\n compliance?: boolean;\n approvals?: boolean;\n apiDocs?: boolean;\n };\n frontmatter?: {\n required?: string[];\n optional?: string[];\n custom?: Record<string, 'string' | 'number' | 'boolean' | 'array'>;\n };\n variables?: Record<string, string>;\n}\n\nconst DEFAULT_CONFIG: LeanSpecConfig = {\n template: 'spec-template.md',\n templates: {\n default: 'spec-template.md',\n },\n specsDir: 'specs',\n structure: {\n pattern: 'flat', // Default to flat for new projects\n prefix: '', // No prefix by default - global sequence numbers only\n dateFormat: 'YYYYMMDD',\n sequenceDigits: 3,\n defaultFile: 'README.md',\n },\n features: {\n aiAgents: true,\n examples: true,\n },\n};\n\nexport async function loadConfig(cwd: string = process.cwd()): Promise<LeanSpecConfig> {\n const configPath = path.join(cwd, '.lean-spec', 'config.json');\n\n try {\n const content = await fs.readFile(configPath, 'utf-8');\n const userConfig = JSON.parse(content);\n const merged = { ...DEFAULT_CONFIG, ...userConfig };\n \n // Normalize legacy pattern format\n normalizeLegacyPattern(merged);\n \n return merged;\n } catch {\n // No config file, use defaults\n return DEFAULT_CONFIG;\n }\n}\n\nexport async function saveConfig(\n config: LeanSpecConfig,\n cwd: string = process.cwd(),\n): Promise<void> {\n const configDir = path.join(cwd, '.lean-spec');\n const configPath = path.join(configDir, 'config.json');\n\n await fs.mkdir(configDir, { recursive: true });\n await fs.writeFile(configPath, JSON.stringify(config, null, 2), 'utf-8');\n}\n\nexport function getToday(format: string = 'YYYYMMDD'): string {\n const now = new Date();\n const year = now.getFullYear();\n const month = String(now.getMonth() + 1).padStart(2, '0');\n const day = String(now.getDate()).padStart(2, '0');\n\n switch (format) {\n case 'YYYYMMDD':\n return `${year}${month}${day}`;\n case 'YYYY-MM-DD':\n return `${year}-${month}-${day}`;\n case 'YYYY-MM':\n return `${year}-${month}`;\n case 'YYYY/MM':\n return `${year}/${month}`;\n case 'YYYY':\n return String(year);\n case 'MM':\n return month;\n case 'DD':\n return day;\n default:\n return `${year}${month}${day}`;\n }\n}\n\n/**\n * Detect if a config uses legacy pattern format and convert it\n */\nexport function normalizeLegacyPattern(config: LeanSpecConfig): void {\n const pattern = config.structure.pattern;\n \n // If pattern contains {date}/{seq}-{name}/, convert to custom with date grouping\n if (pattern && pattern.includes('{date}') && pattern.includes('{seq}') && pattern.includes('{name}')) {\n config.structure.pattern = 'custom';\n config.structure.groupExtractor = `{${config.structure.dateFormat}}`;\n }\n}\n\n/**\n * Resolve prefix string for flat pattern (e.g., \"{YYYYMMDD}-\" becomes \"20251103-\")\n */\nexport function resolvePrefix(\n prefix: string,\n dateFormat: string = 'YYYYMMDD'\n): string {\n const dateReplacements: Record<string, () => string> = {\n '{YYYYMMDD}': () => getToday('YYYYMMDD'),\n '{YYYY-MM-DD}': () => getToday('YYYY-MM-DD'),\n '{YYYY-MM}': () => getToday('YYYY-MM'),\n '{YYYY}': () => getToday('YYYY'),\n '{MM}': () => getToday('MM'),\n '{DD}': () => getToday('DD'),\n };\n\n let result = prefix;\n for (const [pattern, fn] of Object.entries(dateReplacements)) {\n result = result.replace(pattern, fn());\n }\n\n return result;\n}\n\n/**\n * Extract group folder from extractor pattern\n */\nexport function extractGroup(\n extractor: string,\n dateFormat: string = 'YYYYMMDD',\n fields?: Record<string, unknown>,\n fallback?: string\n): string {\n const dateReplacements: Record<string, () => string> = {\n '{YYYYMMDD}': () => getToday('YYYYMMDD'),\n '{YYYY-MM-DD}': () => getToday('YYYY-MM-DD'),\n '{YYYY-MM}': () => getToday('YYYY-MM'),\n '{YYYY}': () => getToday('YYYY'),\n '{MM}': () => getToday('MM'),\n '{DD}': () => getToday('DD'),\n };\n\n let result = extractor;\n\n // Replace date functions first\n for (const [pattern, fn] of Object.entries(dateReplacements)) {\n result = result.replace(pattern, fn());\n }\n\n // Replace frontmatter fields: {fieldname}\n const fieldMatches = result.match(/\\{([^}]+)\\}/g);\n if (fieldMatches) {\n for (const match of fieldMatches) {\n const fieldName = match.slice(1, -1); // Remove { }\n const fieldValue = fields?.[fieldName];\n\n if (fieldValue === undefined) {\n if (!fallback) {\n throw new Error(`Custom field '${fieldName}' required but not provided. Set structure.groupFallback in config or provide --field ${fieldName}=<value>`);\n }\n return fallback;\n }\n\n result = result.replace(match, String(fieldValue));\n }\n }\n\n return result;\n}\n","import * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport { loadConfig } from './config.js';\nimport { parseFrontmatter, getSpecFile, matchesFilter, type SpecFrontmatter, type SpecFilterOptions } from './frontmatter.js';\n\nexport interface SpecInfo {\n path: string; // Relative path like \"20251101/003-pm-visualization-tools\"\n fullPath: string; // Absolute path to spec directory\n filePath: string; // Absolute path to spec file (README.md)\n name: string; // Just the spec name like \"003-pm-visualization-tools\"\n date?: string; // Date folder like \"20251101\" (optional for flat/nested patterns)\n frontmatter: SpecFrontmatter;\n content?: string; // Full file content (optional, for search)\n subFiles?: SubFileInfo[]; // Sub-documents and assets\n}\n\nexport interface SubFileInfo {\n name: string; // e.g., \"TESTING.md\" or \"diagram.png\"\n path: string; // Absolute path to the file\n size: number; // File size in bytes\n type: 'document' | 'asset'; // Classification based on file type\n content?: string; // Optional content for documents\n}\n\n// Load sub-files for a spec (all files except README.md)\nexport async function loadSubFiles(\n specDir: string,\n options: { includeContent?: boolean } = {}\n): Promise<SubFileInfo[]> {\n const subFiles: SubFileInfo[] = [];\n\n try {\n const entries = await fs.readdir(specDir, { withFileTypes: true });\n\n for (const entry of entries) {\n // Skip README.md (main spec file)\n if (entry.name === 'README.md') continue;\n\n // Skip directories for now (could be assets folder)\n if (entry.isDirectory()) continue;\n\n const filePath = path.join(specDir, entry.name);\n const stat = await fs.stat(filePath);\n\n // Determine type based on extension\n const ext = path.extname(entry.name).toLowerCase();\n const isDocument = ext === '.md';\n\n const subFile: SubFileInfo = {\n name: entry.name,\n path: filePath,\n size: stat.size,\n type: isDocument ? 'document' : 'asset',\n };\n\n // Load content for documents if requested\n if (isDocument && options.includeContent) {\n subFile.content = await fs.readFile(filePath, 'utf-8');\n }\n\n subFiles.push(subFile);\n }\n } catch (error) {\n // Directory doesn't exist or can't be read - return empty array\n // This is expected for specs without sub-files\n return [];\n }\n\n // Sort: documents first, then alphabetically\n return subFiles.sort((a, b) => {\n if (a.type !== b.type) {\n return a.type === 'document' ? -1 : 1;\n }\n return a.name.localeCompare(b.name);\n });\n}\n\n// Load all specs from the specs directory\nexport async function loadAllSpecs(options: {\n includeArchived?: boolean;\n includeContent?: boolean;\n includeSubFiles?: boolean;\n filter?: SpecFilterOptions;\n sortBy?: string;\n sortOrder?: 'asc' | 'desc';\n} = {}): Promise<SpecInfo[]> {\n const config = await loadConfig();\n const cwd = process.cwd();\n const specsDir = path.join(cwd, config.specsDir);\n\n const specs: SpecInfo[] = [];\n\n // Check if specs directory exists\n try {\n await fs.access(specsDir);\n } catch {\n return [];\n }\n\n // Pattern to match spec directories (2 or more digits followed by dash)\n const specPattern = /^(\\d{2,})-/;\n\n // Recursively load all specs from the directory structure\n async function loadSpecsFromDir(dir: string, relativePath: string = ''): Promise<void> {\n try {\n const entries = await fs.readdir(dir, { withFileTypes: true });\n \n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n \n // Skip archived directory in main scan (handle separately)\n if (entry.name === 'archived' && relativePath === '') continue;\n \n const entryPath = path.join(dir, entry.name);\n const entryRelativePath = relativePath ? `${relativePath}/${entry.name}` : entry.name;\n \n // Check if this is a spec directory (NNN-name format)\n if (specPattern.test(entry.name)) {\n const specFile = await getSpecFile(entryPath, config.structure.defaultFile);\n \n if (specFile) {\n const frontmatter = await parseFrontmatter(specFile, config);\n \n if (frontmatter) {\n // Apply filter if provided\n if (options.filter && !matchesFilter(frontmatter, options.filter)) {\n continue;\n }\n \n // Extract date from path or frontmatter\n const dateMatch = entryRelativePath.match(/(\\d{8})/);\n let date: string;\n \n if (dateMatch) {\n date = dateMatch[1];\n } else if (typeof frontmatter.created === 'string') {\n date = frontmatter.created;\n } else if (frontmatter.created) {\n date = String(frontmatter.created);\n } else {\n date = '';\n }\n \n const specInfo: SpecInfo = {\n path: entryRelativePath,\n fullPath: entryPath,\n filePath: specFile,\n name: entry.name,\n date: date,\n frontmatter,\n };\n \n // Load content if requested\n if (options.includeContent) {\n specInfo.content = await fs.readFile(specFile, 'utf-8');\n }\n \n // Load sub-files if requested\n if (options.includeSubFiles) {\n specInfo.subFiles = await loadSubFiles(entryPath, {\n includeContent: options.includeContent,\n });\n }\n \n specs.push(specInfo);\n }\n }\n } else {\n // Not a spec directory, scan recursively for nested structure\n await loadSpecsFromDir(entryPath, entryRelativePath);\n }\n }\n } catch {\n // Directory doesn't exist or can't be read\n }\n }\n \n // Load active specs\n await loadSpecsFromDir(specsDir);\n\n // Load archived specs if requested\n if (options.includeArchived) {\n const archivedPath = path.join(specsDir, 'archived');\n await loadSpecsFromDir(archivedPath, 'archived');\n }\n\n // Sort specs based on options (default: id desc)\n const sortBy = options.sortBy || 'id';\n const sortOrder = options.sortOrder || 'desc';\n \n specs.sort((a, b) => {\n let comparison = 0;\n \n switch (sortBy) {\n case 'id':\n case 'number': { // Keep 'number' for backwards compatibility\n // Extract leading digits from spec name\n const aNum = parseInt(a.name.match(/^(\\d+)/)?.[1] || '0', 10);\n const bNum = parseInt(b.name.match(/^(\\d+)/)?.[1] || '0', 10);\n comparison = aNum - bNum;\n break;\n }\n case 'created': {\n // Sort by created date from frontmatter\n const aDate = String(a.frontmatter.created || '');\n const bDate = String(b.frontmatter.created || '');\n comparison = aDate.localeCompare(bDate);\n break;\n }\n case 'name': {\n comparison = a.name.localeCompare(b.name);\n break;\n }\n case 'status': {\n comparison = a.frontmatter.status.localeCompare(b.frontmatter.status);\n break;\n }\n case 'priority': {\n // Priority order: critical > high > medium > low > (none)\n const priorityOrder = { critical: 4, high: 3, medium: 2, low: 1 };\n const aPriority = a.frontmatter.priority ? priorityOrder[a.frontmatter.priority] : 0;\n const bPriority = b.frontmatter.priority ? priorityOrder[b.frontmatter.priority] : 0;\n comparison = aPriority - bPriority;\n break;\n }\n default:\n // Default to created date\n const aDate = String(a.frontmatter.created || '');\n const bDate = String(b.frontmatter.created || '');\n comparison = aDate.localeCompare(bDate);\n }\n \n // Apply sort order\n return sortOrder === 'desc' ? -comparison : comparison;\n });\n\n return specs;\n}\n\n// Get a specific spec by path\nexport async function getSpec(specPath: string): Promise<SpecInfo | null> {\n const config = await loadConfig();\n const cwd = process.cwd();\n const specsDir = path.join(cwd, config.specsDir);\n\n // Resolve the full path\n let fullPath: string;\n if (path.isAbsolute(specPath)) {\n fullPath = specPath;\n } else {\n fullPath = path.join(specsDir, specPath);\n }\n\n // Check if directory exists\n try {\n await fs.access(fullPath);\n } catch {\n return null;\n }\n\n const specFile = await getSpecFile(fullPath, config.structure.defaultFile);\n if (!specFile) return null;\n\n const frontmatter = await parseFrontmatter(specFile, config);\n if (!frontmatter) return null;\n\n const content = await fs.readFile(specFile, 'utf-8');\n\n // Parse path components\n const relativePath = path.relative(specsDir, fullPath);\n const parts = relativePath.split(path.sep);\n const date = parts[0] === 'archived' ? parts[1] : parts[0];\n const name = parts[parts.length - 1];\n\n return {\n path: relativePath,\n fullPath,\n filePath: specFile,\n name,\n date,\n frontmatter,\n content,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/config.ts","../src/spec-loader.ts"],"names":["path","fs","stat","aDate","bDate"],"mappings":"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","file":"chunk-RF5PKL6L.js","sourcesContent":["import * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\n\nexport interface LeanSpecConfig {\n template: string;\n templates?: Record<string, string>; // Maps template name to filename\n specsDir: string;\n autoCheck?: boolean; // Enable/disable auto-check for sequence conflicts (default: true)\n structure: {\n pattern: 'flat' | 'custom' | string; // 'flat' or 'custom', or legacy pattern string\n dateFormat: string;\n sequenceDigits: number;\n defaultFile: string;\n prefix?: string; // For flat pattern: \"{YYYYMMDD}-\" or \"spec-\" (optional, default: empty for global numbering)\n groupExtractor?: string; // For custom pattern: \"{YYYYMMDD}\" or \"milestone-{milestone}\"\n groupFallback?: string; // Fallback folder if field missing (only for non-date extractors)\n };\n features?: {\n aiAgents?: boolean;\n examples?: boolean;\n collaboration?: boolean;\n compliance?: boolean;\n approvals?: boolean;\n apiDocs?: boolean;\n };\n frontmatter?: {\n required?: string[];\n optional?: string[];\n custom?: Record<string, 'string' | 'number' | 'boolean' | 'array'>;\n };\n variables?: Record<string, string>;\n}\n\nconst DEFAULT_CONFIG: LeanSpecConfig = {\n template: 'spec-template.md',\n templates: {\n default: 'spec-template.md',\n },\n specsDir: 'specs',\n structure: {\n pattern: 'flat', // Default to flat for new projects\n prefix: '', // No prefix by default - global sequence numbers only\n dateFormat: 'YYYYMMDD',\n sequenceDigits: 3,\n defaultFile: 'README.md',\n },\n features: {\n aiAgents: true,\n examples: true,\n },\n};\n\nexport async function loadConfig(cwd: string = process.cwd()): Promise<LeanSpecConfig> {\n const configPath = path.join(cwd, '.lean-spec', 'config.json');\n\n try {\n const content = await fs.readFile(configPath, 'utf-8');\n const userConfig = JSON.parse(content);\n const merged = { ...DEFAULT_CONFIG, ...userConfig };\n \n // Normalize legacy pattern format\n normalizeLegacyPattern(merged);\n \n return merged;\n } catch {\n // No config file, use defaults\n return DEFAULT_CONFIG;\n }\n}\n\nexport async function saveConfig(\n config: LeanSpecConfig,\n cwd: string = process.cwd(),\n): Promise<void> {\n const configDir = path.join(cwd, '.lean-spec');\n const configPath = path.join(configDir, 'config.json');\n\n await fs.mkdir(configDir, { recursive: true });\n await fs.writeFile(configPath, JSON.stringify(config, null, 2), 'utf-8');\n}\n\nexport function getToday(format: string = 'YYYYMMDD'): string {\n const now = new Date();\n const year = now.getFullYear();\n const month = String(now.getMonth() + 1).padStart(2, '0');\n const day = String(now.getDate()).padStart(2, '0');\n\n switch (format) {\n case 'YYYYMMDD':\n return `${year}${month}${day}`;\n case 'YYYY-MM-DD':\n return `${year}-${month}-${day}`;\n case 'YYYY-MM':\n return `${year}-${month}`;\n case 'YYYY/MM':\n return `${year}/${month}`;\n case 'YYYY':\n return String(year);\n case 'MM':\n return month;\n case 'DD':\n return day;\n default:\n return `${year}${month}${day}`;\n }\n}\n\n/**\n * Detect if a config uses legacy pattern format and convert it\n */\nexport function normalizeLegacyPattern(config: LeanSpecConfig): void {\n const pattern = config.structure.pattern;\n \n // If pattern contains {date}/{seq}-{name}/, convert to custom with date grouping\n if (pattern && pattern.includes('{date}') && pattern.includes('{seq}') && pattern.includes('{name}')) {\n config.structure.pattern = 'custom';\n config.structure.groupExtractor = `{${config.structure.dateFormat}}`;\n }\n}\n\n/**\n * Resolve prefix string for flat pattern (e.g., \"{YYYYMMDD}-\" becomes \"20251103-\")\n */\nexport function resolvePrefix(\n prefix: string,\n dateFormat: string = 'YYYYMMDD'\n): string {\n const dateReplacements: Record<string, () => string> = {\n '{YYYYMMDD}': () => getToday('YYYYMMDD'),\n '{YYYY-MM-DD}': () => getToday('YYYY-MM-DD'),\n '{YYYY-MM}': () => getToday('YYYY-MM'),\n '{YYYY}': () => getToday('YYYY'),\n '{MM}': () => getToday('MM'),\n '{DD}': () => getToday('DD'),\n };\n\n let result = prefix;\n for (const [pattern, fn] of Object.entries(dateReplacements)) {\n result = result.replace(pattern, fn());\n }\n\n return result;\n}\n\n/**\n * Extract group folder from extractor pattern\n */\nexport function extractGroup(\n extractor: string,\n dateFormat: string = 'YYYYMMDD',\n fields?: Record<string, unknown>,\n fallback?: string\n): string {\n const dateReplacements: Record<string, () => string> = {\n '{YYYYMMDD}': () => getToday('YYYYMMDD'),\n '{YYYY-MM-DD}': () => getToday('YYYY-MM-DD'),\n '{YYYY-MM}': () => getToday('YYYY-MM'),\n '{YYYY}': () => getToday('YYYY'),\n '{MM}': () => getToday('MM'),\n '{DD}': () => getToday('DD'),\n };\n\n let result = extractor;\n\n // Replace date functions first\n for (const [pattern, fn] of Object.entries(dateReplacements)) {\n result = result.replace(pattern, fn());\n }\n\n // Replace frontmatter fields: {fieldname}\n const fieldMatches = result.match(/\\{([^}]+)\\}/g);\n if (fieldMatches) {\n for (const match of fieldMatches) {\n const fieldName = match.slice(1, -1); // Remove { }\n const fieldValue = fields?.[fieldName];\n\n if (fieldValue === undefined) {\n if (!fallback) {\n throw new Error(`Custom field '${fieldName}' required but not provided. Set structure.groupFallback in config or provide --field ${fieldName}=<value>`);\n }\n return fallback;\n }\n\n result = result.replace(match, String(fieldValue));\n }\n }\n\n return result;\n}\n","import * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport { loadConfig } from './config.js';\nimport { parseFrontmatter, getSpecFile, matchesFilter, type SpecFrontmatter, type SpecFilterOptions } from './frontmatter.js';\n\nexport interface SpecInfo {\n path: string; // Relative path like \"20251101/003-pm-visualization-tools\"\n fullPath: string; // Absolute path to spec directory\n filePath: string; // Absolute path to spec file (README.md)\n name: string; // Just the spec name like \"003-pm-visualization-tools\"\n date?: string; // Date folder like \"20251101\" (optional for flat/nested patterns)\n frontmatter: SpecFrontmatter;\n content?: string; // Full file content (optional, for search)\n subFiles?: SubFileInfo[]; // Sub-documents and assets\n}\n\nexport interface SubFileInfo {\n name: string; // e.g., \"TESTING.md\" or \"diagram.png\"\n path: string; // Absolute path to the file\n size: number; // File size in bytes\n type: 'document' | 'asset'; // Classification based on file type\n content?: string; // Optional content for documents\n}\n\n// Load sub-files for a spec (all files except README.md)\nexport async function loadSubFiles(\n specDir: string,\n options: { includeContent?: boolean } = {}\n): Promise<SubFileInfo[]> {\n const subFiles: SubFileInfo[] = [];\n\n try {\n const entries = await fs.readdir(specDir, { withFileTypes: true });\n\n for (const entry of entries) {\n // Skip README.md (main spec file)\n if (entry.name === 'README.md') continue;\n\n // Skip directories for now (could be assets folder)\n if (entry.isDirectory()) continue;\n\n const filePath = path.join(specDir, entry.name);\n const stat = await fs.stat(filePath);\n\n // Determine type based on extension\n const ext = path.extname(entry.name).toLowerCase();\n const isDocument = ext === '.md';\n\n const subFile: SubFileInfo = {\n name: entry.name,\n path: filePath,\n size: stat.size,\n type: isDocument ? 'document' : 'asset',\n };\n\n // Load content for documents if requested\n if (isDocument && options.includeContent) {\n subFile.content = await fs.readFile(filePath, 'utf-8');\n }\n\n subFiles.push(subFile);\n }\n } catch (error) {\n // Directory doesn't exist or can't be read - return empty array\n // This is expected for specs without sub-files\n return [];\n }\n\n // Sort: documents first, then alphabetically\n return subFiles.sort((a, b) => {\n if (a.type !== b.type) {\n return a.type === 'document' ? -1 : 1;\n }\n return a.name.localeCompare(b.name);\n });\n}\n\n// Load all specs from the specs directory\nexport async function loadAllSpecs(options: {\n includeArchived?: boolean;\n includeContent?: boolean;\n includeSubFiles?: boolean;\n filter?: SpecFilterOptions;\n sortBy?: string;\n sortOrder?: 'asc' | 'desc';\n} = {}): Promise<SpecInfo[]> {\n const config = await loadConfig();\n const cwd = process.cwd();\n const specsDir = path.join(cwd, config.specsDir);\n\n const specs: SpecInfo[] = [];\n\n // Check if specs directory exists\n try {\n await fs.access(specsDir);\n } catch {\n return [];\n }\n\n // Pattern to match spec directories (2 or more digits followed by dash)\n const specPattern = /^(\\d{2,})-/;\n\n // Recursively load all specs from the directory structure\n async function loadSpecsFromDir(dir: string, relativePath: string = ''): Promise<void> {\n try {\n const entries = await fs.readdir(dir, { withFileTypes: true });\n \n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n \n // Skip archived directory in main scan (handle separately)\n if (entry.name === 'archived' && relativePath === '') continue;\n \n const entryPath = path.join(dir, entry.name);\n const entryRelativePath = relativePath ? `${relativePath}/${entry.name}` : entry.name;\n \n // Check if this is a spec directory (NNN-name format)\n if (specPattern.test(entry.name)) {\n const specFile = await getSpecFile(entryPath, config.structure.defaultFile);\n \n if (specFile) {\n const frontmatter = await parseFrontmatter(specFile, config);\n \n if (frontmatter) {\n // Apply filter if provided\n if (options.filter && !matchesFilter(frontmatter, options.filter)) {\n continue;\n }\n \n // Extract date from path or frontmatter\n const dateMatch = entryRelativePath.match(/(\\d{8})/);\n let date: string;\n \n if (dateMatch) {\n date = dateMatch[1];\n } else if (typeof frontmatter.created === 'string') {\n date = frontmatter.created;\n } else if (frontmatter.created) {\n date = String(frontmatter.created);\n } else {\n date = '';\n }\n \n const specInfo: SpecInfo = {\n path: entryRelativePath,\n fullPath: entryPath,\n filePath: specFile,\n name: entry.name,\n date: date,\n frontmatter,\n };\n \n // Load content if requested\n if (options.includeContent) {\n specInfo.content = await fs.readFile(specFile, 'utf-8');\n }\n \n // Load sub-files if requested\n if (options.includeSubFiles) {\n specInfo.subFiles = await loadSubFiles(entryPath, {\n includeContent: options.includeContent,\n });\n }\n \n specs.push(specInfo);\n }\n }\n } else {\n // Not a spec directory, scan recursively for nested structure\n await loadSpecsFromDir(entryPath, entryRelativePath);\n }\n }\n } catch {\n // Directory doesn't exist or can't be read\n }\n }\n \n // Load active specs\n await loadSpecsFromDir(specsDir);\n\n // Load archived specs if requested\n if (options.includeArchived) {\n const archivedPath = path.join(specsDir, 'archived');\n await loadSpecsFromDir(archivedPath, 'archived');\n }\n\n // Sort specs based on options (default: id desc)\n const sortBy = options.sortBy || 'id';\n const sortOrder = options.sortOrder || 'desc';\n \n specs.sort((a, b) => {\n let comparison = 0;\n \n switch (sortBy) {\n case 'id':\n case 'number': { // Keep 'number' for backwards compatibility\n // Extract leading digits from spec name\n const aNum = parseInt(a.name.match(/^(\\d+)/)?.[1] || '0', 10);\n const bNum = parseInt(b.name.match(/^(\\d+)/)?.[1] || '0', 10);\n comparison = aNum - bNum;\n break;\n }\n case 'created': {\n // Sort by created date from frontmatter\n const aDate = String(a.frontmatter.created || '');\n const bDate = String(b.frontmatter.created || '');\n comparison = aDate.localeCompare(bDate);\n break;\n }\n case 'name': {\n comparison = a.name.localeCompare(b.name);\n break;\n }\n case 'status': {\n comparison = a.frontmatter.status.localeCompare(b.frontmatter.status);\n break;\n }\n case 'priority': {\n // Priority order: critical > high > medium > low > (none)\n const priorityOrder = { critical: 4, high: 3, medium: 2, low: 1 };\n const aPriority = a.frontmatter.priority ? priorityOrder[a.frontmatter.priority] : 0;\n const bPriority = b.frontmatter.priority ? priorityOrder[b.frontmatter.priority] : 0;\n comparison = aPriority - bPriority;\n break;\n }\n default:\n // Default to created date\n const aDate = String(a.frontmatter.created || '');\n const bDate = String(b.frontmatter.created || '');\n comparison = aDate.localeCompare(bDate);\n }\n \n // Apply sort order\n return sortOrder === 'desc' ? -comparison : comparison;\n });\n\n return specs;\n}\n\n// Get a specific spec by path\nexport async function getSpec(specPath: string): Promise<SpecInfo | null> {\n const config = await loadConfig();\n const cwd = process.cwd();\n const specsDir = path.join(cwd, config.specsDir);\n\n // Resolve the full path\n let fullPath: string;\n if (path.isAbsolute(specPath)) {\n fullPath = specPath;\n } else {\n fullPath = path.join(specsDir, specPath);\n }\n\n // Check if directory exists\n try {\n await fs.access(fullPath);\n } catch {\n return null;\n }\n\n const specFile = await getSpecFile(fullPath, config.structure.defaultFile);\n if (!specFile) return null;\n\n const frontmatter = await parseFrontmatter(specFile, config);\n if (!frontmatter) return null;\n\n const content = await fs.readFile(specFile, 'utf-8');\n\n // Parse path components\n const relativePath = path.relative(specsDir, fullPath);\n const parts = relativePath.split(path.sep);\n const date = parts[0] === 'archived' ? parts[1] : parts[0];\n const name = parts[parts.length - 1];\n\n return {\n path: relativePath,\n fullPath,\n filePath: specFile,\n name,\n date,\n frontmatter,\n content,\n };\n}\n"]}
|
|
@@ -201,6 +201,9 @@ async function updateFrontmatter(filePath, updates) {
|
|
|
201
201
|
await fs.writeFile(filePath, newContent, "utf-8");
|
|
202
202
|
}
|
|
203
203
|
function updateVisualMetadata(content, frontmatter) {
|
|
204
|
+
if (!frontmatter.status || !frontmatter.created) {
|
|
205
|
+
return content;
|
|
206
|
+
}
|
|
204
207
|
const statusEmoji = getStatusEmojiPlain(frontmatter.status);
|
|
205
208
|
const statusLabel = frontmatter.status.charAt(0).toUpperCase() + frontmatter.status.slice(1).replace("-", " ");
|
|
206
209
|
const created = dayjs(frontmatter.created).format("YYYY-MM-DD");
|
|
@@ -293,5 +296,5 @@ function matchesFilter(frontmatter, filter) {
|
|
|
293
296
|
}
|
|
294
297
|
|
|
295
298
|
export { enrichWithTimestamps, getSpecFile, matchesFilter, normalizeDateFields, normalizeTagsField, parseFrontmatter, updateFrontmatter, validateCustomField, validateCustomFields };
|
|
296
|
-
//# sourceMappingURL=chunk-
|
|
297
|
-
//# sourceMappingURL=chunk-
|
|
299
|
+
//# sourceMappingURL=chunk-VN5BUHTV.js.map
|
|
300
|
+
//# sourceMappingURL=chunk-VN5BUHTV.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/frontmatter.ts"],"names":[],"mappings":";;;;;;;AAuDO,SAAS,oBAAoB,IAAA,EAAqC;AACvE,EAAA,MAAM,UAAA,GAAa,CAAC,SAAA,EAAW,WAAA,EAAa,WAAW,KAAK,CAAA;AAE5D,EAAA,KAAA,MAAW,SAAS,UAAA,EAAY;AAC9B,IAAA,IAAI,IAAA,CAAK,KAAK,CAAA,YAAa,IAAA,EAAM;AAC/B,MAAA,IAAA,CAAK,KAAK,CAAA,GAAK,IAAA,CAAK,KAAK,CAAA,CAAW,aAAY,CAAE,KAAA,CAAM,GAAG,CAAA,CAAE,CAAC,CAAA;AAAA,IAChE;AAAA,EACF;AACF;AAMO,SAAS,oBAAA,CACd,MACA,YAAA,EACM;AACN,EAAA,MAAM,GAAA,GAAA,iBAAM,IAAI,IAAA,EAAK,EAAE,WAAA,EAAY;AAInC,EAAA,IAAI,CAAC,KAAK,UAAA,EAAY;AACpB,IAAA,IAAA,CAAK,UAAA,GAAa,GAAA;AAAA,EACpB;AAGA,EAAA,IAAI,YAAA,EAAc;AAChB,IAAA,IAAA,CAAK,UAAA,GAAa,GAAA;AAAA,EACpB;AAGA,EAAA,IACE,IAAA,CAAK,WAAW,UAAA,IAChB,YAAA,EAAc,WAAW,UAAA,IACzB,CAAC,KAAK,YAAA,EACN;AACA,IAAA,IAAA,CAAK,YAAA,GAAe,GAAA;AAEpB,IAAA,IAAI,CAAC,KAAK,SAAA,EAAW;AACnB,MAAA,IAAA,CAAK,SAAA,GAAA,qBAAgB,IAAA,EAAK,EAAE,aAAY,CAAE,KAAA,CAAM,GAAG,CAAA,CAAE,CAAC,CAAA;AAAA,IACxD;AAAA,EACF;AAGA,EAAA,IAAI,YAAA,IAAgB,IAAA,CAAK,MAAA,KAAW,YAAA,CAAa,MAAA,EAAQ;AACvD,IAAA,IAAI,CAAC,KAAA,CAAM,OAAA,CAAQ,IAAA,CAAK,WAAW,CAAA,EAAG;AACpC,MAAA,IAAA,CAAK,cAAc,EAAC;AAAA,IACtB;AACA,IAAC,IAAA,CAAK,YAAmC,IAAA,CAAK;AAAA,MAC5C,QAAQ,IAAA,CAAK,MAAA;AAAA,MACb,EAAA,EAAI;AAAA,KACL,CAAA;AAAA,EACH;AACF;AAMO,SAAS,mBAAmB,IAAA,EAAqC;AACtE,EAAA,IAAI,IAAA,CAAK,IAAA,IAAQ,OAAO,IAAA,CAAK,SAAS,QAAA,EAAU;AAC9C,IAAA,IAAI;AAEF,MAAA,MAAM,MAAA,GAAS,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,IAAc,CAAA;AAC7C,MAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,MAAM,CAAA,EAAG;AACzB,QAAA,IAAA,CAAK,IAAA,GAAO,MAAA;AAAA,MACd;AAAA,IACF,CAAA,CAAA,MAAQ;AAEN,MAAA,IAAA,CAAK,IAAA,GAAQ,IAAA,CAAK,IAAA,CAAgB,KAAA,CAAM,GAAG,EAAE,GAAA,CAAI,CAAA,CAAA,KAAK,CAAA,CAAE,IAAA,EAAM,CAAA;AAAA,IAChE;AAAA,EACF;AACF;AAKO,SAAS,mBAAA,CACd,OACA,YAAA,EACuD;AACvD,EAAA,QAAQ,YAAA;AAAc,IACpB,KAAK,QAAA;AACH,MAAA,IAAI,OAAO,UAAU,QAAA,EAAU;AAC7B,QAAA,OAAO,EAAE,KAAA,EAAO,IAAA,EAAM,OAAA,EAAS,KAAA,EAAM;AAAA,MACvC;AAEA,MAAA,OAAO,EAAE,KAAA,EAAO,IAAA,EAAM,OAAA,EAAS,MAAA,CAAO,KAAK,CAAA,EAAE;AAAA,IAE/C,KAAK,QAAA;AACH,MAAA,IAAI,OAAO,UAAU,QAAA,EAAU;AAC7B,QAAA,OAAO,EAAE,KAAA,EAAO,IAAA,EAAM,OAAA,EAAS,KAAA,EAAM;AAAA,MACvC;AAEA,MAAA,MAAM,GAAA,GAAM,OAAO,KAAK,CAAA;AACxB,MAAA,IAAI,CAAC,KAAA,CAAM,GAAG,CAAA,EAAG;AACf,QAAA,OAAO,EAAE,KAAA,EAAO,IAAA,EAAM,OAAA,EAAS,GAAA,EAAI;AAAA,MACrC;AACA,MAAA,OAAO,EAAE,KAAA,EAAO,KAAA,EAAO,KAAA,EAAO,CAAA,gBAAA,EAAmB,KAAK,CAAA,WAAA,CAAA,EAAc;AAAA,IAEtE,KAAK,SAAA;AACH,MAAA,IAAI,OAAO,UAAU,SAAA,EAAW;AAC9B,QAAA,OAAO,EAAE,KAAA,EAAO,IAAA,EAAM,OAAA,EAAS,KAAA,EAAM;AAAA,MACvC;AAEA,MAAA,IAAI,KAAA,KAAU,MAAA,IAAU,KAAA,KAAU,KAAA,IAAS,UAAU,GAAA,EAAK;AACxD,QAAA,OAAO,EAAE,KAAA,EAAO,IAAA,EAAM,OAAA,EAAS,IAAA,EAAK;AAAA,MACtC;AACA,MAAA,IAAI,KAAA,KAAU,OAAA,IAAW,KAAA,KAAU,IAAA,IAAQ,UAAU,GAAA,EAAK;AACxD,QAAA,OAAO,EAAE,KAAA,EAAO,IAAA,EAAM,OAAA,EAAS,KAAA,EAAM;AAAA,MACvC;AACA,MAAA,OAAO,EAAE,KAAA,EAAO,KAAA,EAAO,KAAA,EAAO,CAAA,gBAAA,EAAmB,KAAK,CAAA,YAAA,CAAA,EAAe;AAAA,IAEvE,KAAK,OAAA;AACH,MAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,EAAG;AACxB,QAAA,OAAO,EAAE,KAAA,EAAO,IAAA,EAAM,OAAA,EAAS,KAAA,EAAM;AAAA,MACvC;AACA,MAAA,OAAO,EAAE,KAAA,EAAO,KAAA,EAAO,OAAO,CAAA,uBAAA,EAA0B,OAAO,KAAK,CAAA,CAAA,EAAG;AAAA,IAEzE;AACE,MAAA,OAAO,EAAE,KAAA,EAAO,KAAA,EAAO,KAAA,EAAO,CAAA,cAAA,EAAiB,YAAY,CAAA,CAAA,EAAG;AAAA;AAEpE;AAKO,SAAS,oBAAA,CACd,aACA,MAAA,EACyB;AACzB,EAAA,IAAI,CAAC,MAAA,EAAQ,WAAA,EAAa,MAAA,EAAQ;AAChC,IAAA,OAAO,WAAA;AAAA,EACT;AAEA,EAAA,MAAM,MAAA,GAAS,EAAE,GAAG,WAAA,EAAY;AAEhC,EAAA,KAAA,MAAW,CAAC,WAAW,YAAY,CAAA,IAAK,OAAO,OAAA,CAAQ,MAAA,CAAO,WAAA,CAAY,MAAM,CAAA,EAAG;AACjF,IAAA,IAAI,aAAa,MAAA,EAAQ;AACvB,MAAA,MAAM,UAAA,GAAa,mBAAA,CAAoB,MAAA,CAAO,SAAS,GAAG,YAAY,CAAA;AACtE,MAAA,IAAI,WAAW,KAAA,EAAO;AACpB,QAAA,MAAA,CAAO,SAAS,IAAI,UAAA,CAAW,OAAA;AAAA,MACjC,CAAA,MAAO;AACL,QAAA,OAAA,CAAQ,KAAK,CAAA,+BAAA,EAAkC,SAAS,CAAA,GAAA,EAAM,UAAA,CAAW,KAAK,CAAA,CAAE,CAAA;AAAA,MAClF;AAAA,IACF;AAAA,EACF;AAEA,EAAA,OAAO,MAAA;AACT;AAGA,eAAsB,gBAAA,CACpB,UACA,MAAA,EACiC;AACjC,EAAA,IAAI;AACF,IAAA,MAAM,OAAA,GAAU,MAAS,EAAA,CAAA,QAAA,CAAS,QAAA,EAAU,OAAO,CAAA;AACnD,IAAA,MAAM,MAAA,GAAS,OAAO,OAAA,EAAS;AAAA,MAC7B,OAAA,EAAS;AAAA,QACP,IAAA,EAAM,CAAC,GAAA,KAAQ,IAAA,CAAK,IAAA,CAAK,KAAK,EAAE,MAAA,EAAQ,IAAA,CAAK,eAAA,EAAiB;AAAA;AAChE,KACD,CAAA;AAED,IAAA,IAAI,CAAC,OAAO,IAAA,IAAQ,MAAA,CAAO,KAAK,MAAA,CAAO,IAAI,CAAA,CAAE,MAAA,KAAW,CAAA,EAAG;AAEzD,MAAA,OAAO,oBAAoB,OAAO,CAAA;AAAA,IACpC;AAGA,IAAA,IAAI,CAAC,MAAA,CAAO,IAAA,CAAK,MAAA,EAAQ;AACvB,MAAA,OAAA,CAAQ,IAAA,CAAK,CAAA,4CAAA,EAA+C,QAAQ,CAAA,CAAE,CAAA;AACtE,MAAA,OAAO,IAAA;AAAA,IACT;AAEA,IAAA,IAAI,CAAC,MAAA,CAAO,IAAA,CAAK,OAAA,EAAS;AACxB,MAAA,OAAA,CAAQ,IAAA,CAAK,CAAA,6CAAA,EAAgD,QAAQ,CAAA,CAAE,CAAA;AACvE,MAAA,OAAO,IAAA;AAAA,IACT;AAGA,IAAA,MAAM,aAAA,GAA8B,CAAC,SAAA,EAAW,aAAA,EAAe,YAAY,UAAU,CAAA;AACrF,IAAA,IAAI,CAAC,aAAA,CAAc,QAAA,CAAS,MAAA,CAAO,IAAA,CAAK,MAAM,CAAA,EAAG;AAC/C,MAAA,OAAA,CAAQ,IAAA,CAAK,CAAA,yBAAA,EAA4B,MAAA,CAAO,IAAA,CAAK,MAAM,CAAA,KAAA,EAAQ,QAAQ,CAAA,gBAAA,EAAmB,aAAA,CAAc,IAAA,CAAK,IAAI,CAAC,CAAA,CAAE,CAAA;AAAA,IAC1H;AAGA,IAAA,IAAI,MAAA,CAAO,KAAK,QAAA,EAAU;AACxB,MAAA,MAAM,eAAA,GAAkC,CAAC,KAAA,EAAO,QAAA,EAAU,QAAQ,UAAU,CAAA;AAC5E,MAAA,IAAI,CAAC,eAAA,CAAgB,QAAA,CAAS,MAAA,CAAO,IAAA,CAAK,QAAQ,CAAA,EAAG;AACnD,QAAA,OAAA,CAAQ,IAAA,CAAK,CAAA,2BAAA,EAA8B,MAAA,CAAO,IAAA,CAAK,QAAQ,CAAA,KAAA,EAAQ,QAAQ,CAAA,gBAAA,EAAmB,eAAA,CAAgB,IAAA,CAAK,IAAI,CAAC,CAAA,CAAE,CAAA;AAAA,MAChI;AAAA,IACF;AAGA,IAAA,kBAAA,CAAmB,OAAO,IAAI,CAAA;AAG9B,IAAA,MAAM,WAAA,GAAc;AAAA,MAClB,QAAA;AAAA,MAAU,SAAA;AAAA,MAAW,MAAA;AAAA,MAAQ,UAAA;AAAA,MAAY,YAAA;AAAA,MACzC,SAAA;AAAA,MAAW,WAAA;AAAA,MAAa,UAAA;AAAA,MAAY,UAAA;AAAA,MAAY,OAAA;AAAA,MAAS,IAAA;AAAA,MAAM,MAAA;AAAA,MAAQ,UAAA;AAAA,MAAY,KAAA;AAAA,MACnF,YAAA;AAAA,MAAc,YAAA;AAAA,MAAc,cAAA;AAAA,MAAgB;AAAA,KAC9C;AAGA,IAAA,MAAM,YAAA,GAAe,MAAA,EAAQ,WAAA,EAAa,MAAA,GAAS,MAAA,CAAO,KAAK,MAAA,CAAO,WAAA,CAAY,MAAM,CAAA,GAAI,EAAC;AAC7F,IAAA,MAAM,cAAA,GAAiB,CAAC,GAAG,WAAA,EAAa,GAAG,YAAY,CAAA;AAEvD,IAAA,MAAM,aAAA,GAAgB,MAAA,CAAO,IAAA,CAAK,MAAA,CAAO,IAAI,CAAA,CAAE,MAAA,CAAO,CAAA,CAAA,KAAK,CAAC,cAAA,CAAe,QAAA,CAAS,CAAC,CAAC,CAAA;AACtF,IAAA,IAAI,aAAA,CAAc,SAAS,CAAA,EAAG;AAC5B,MAAA,OAAA,CAAQ,IAAA,CAAK,2BAA2B,QAAQ,CAAA,EAAA,EAAK,cAAc,IAAA,CAAK,IAAI,CAAC,CAAA,CAAE,CAAA;AAAA,IACjF;AAGA,IAAA,MAAM,aAAA,GAAgB,oBAAA,CAAqB,MAAA,CAAO,IAAA,EAAM,MAAM,CAAA;AAE9D,IAAA,OAAO,aAAA;AAAA,EACT,SAAS,KAAA,EAAO;AACd,IAAA,OAAA,CAAQ,KAAA,CAAM,CAAA,+BAAA,EAAkC,QAAQ,CAAA,CAAA,CAAA,EAAK,KAAK,CAAA;AAClE,IAAA,OAAO,IAAA;AAAA,EACT;AACF;AAGA,SAAS,oBAAoB,OAAA,EAAyC;AACpE,EAAA,MAAM,WAAA,GAAc,OAAA,CAAQ,KAAA,CAAM,6CAA6C,CAAA;AAC/E,EAAA,MAAM,YAAA,GAAe,OAAA,CAAQ,KAAA,CAAM,wCAAwC,CAAA;AAE3E,EAAA,IAAI,eAAe,YAAA,EAAc;AAC/B,IAAA,MAAM,MAAA,GAAS,YAAY,CAAC,CAAA,CAAE,aAAY,CAAE,OAAA,CAAQ,QAAQ,GAAG,CAAA;AAC/D,IAAA,MAAM,OAAA,GAAU,aAAa,CAAC,CAAA;AAE9B,IAAA,OAAO;AAAA,MACL,MAAA;AAAA,MACA;AAAA,KACF;AAAA,EACF;AAEA,EAAA,OAAO,IAAA;AACT;AAGA,eAAsB,iBAAA,CACpB,UACA,OAAA,EACe;AACf,EAAA,MAAM,OAAA,GAAU,MAAS,EAAA,CAAA,QAAA,CAAS,QAAA,EAAU,OAAO,CAAA;AACnD,EAAA,MAAM,MAAA,GAAS,OAAO,OAAA,EAAS;AAAA,IAC7B,OAAA,EAAS;AAAA,MACP,IAAA,EAAM,CAAC,GAAA,KAAQ,IAAA,CAAK,IAAA,CAAK,KAAK,EAAE,MAAA,EAAQ,IAAA,CAAK,eAAA,EAAiB;AAAA;AAChE,GACD,CAAA;AAGD,EAAA,MAAM,YAAA,GAAe,EAAE,GAAG,MAAA,CAAO,IAAA,EAAK;AAGtC,EAAA,MAAM,UAAU,EAAE,GAAG,MAAA,CAAO,IAAA,EAAM,GAAG,OAAA,EAAQ;AAG7C,EAAA,mBAAA,CAAoB,OAAO,CAAA;AAG3B,EAAA,oBAAA,CAAqB,SAAS,YAAY,CAAA;AAG1C,EAAA,IAAI,OAAA,CAAQ,MAAA,KAAW,UAAA,IAAc,CAAC,QAAQ,SAAA,EAAW;AACvD,IAAA,OAAA,CAAQ,SAAA,GAAY,KAAA,EAAM,CAAE,MAAA,CAAO,YAAY,CAAA;AAAA,EACjD;AAEA,EAAA,IAAI,SAAA,IAAa,OAAO,IAAA,EAAM;AAC5B,IAAA,OAAA,CAAQ,OAAA,GAAU,KAAA,EAAM,CAAE,MAAA,CAAO,YAAY,CAAA;AAAA,EAC/C;AAGA,EAAA,IAAI,iBAAiB,MAAA,CAAO,OAAA;AAC5B,EAAA,cAAA,GAAiB,oBAAA,CAAqB,gBAAgB,OAA0B,CAAA;AAGhF,EAAA,MAAM,UAAA,GAAa,MAAA,CAAO,SAAA,CAAU,cAAA,EAAgB,OAAO,CAAA;AAC3D,EAAA,MAAS,EAAA,CAAA,SAAA,CAAU,QAAA,EAAU,UAAA,EAAY,OAAO,CAAA;AAClD;AAGA,SAAS,oBAAA,CAAqB,SAAiB,WAAA,EAAsC;AAEnF,EAAA,IAAI,CAAC,WAAA,CAAY,MAAA,IAAU,CAAC,YAAY,OAAA,EAAS;AAC/C,IAAA,OAAO,OAAA;AAAA,EACT;AAEA,EAAA,MAAM,WAAA,GAAc,mBAAA,CAAoB,WAAA,CAAY,MAAM,CAAA;AAC1D,EAAA,MAAM,WAAA,GAAc,WAAA,CAAY,MAAA,CAAO,MAAA,CAAO,CAAC,CAAA,CAAE,WAAA,EAAY,GAAI,WAAA,CAAY,OAAO,KAAA,CAAM,CAAC,CAAA,CAAE,OAAA,CAAQ,KAAK,GAAG,CAAA;AAG7G,EAAA,MAAM,UAAU,KAAA,CAAM,WAAA,CAAY,OAAO,CAAA,CAAE,OAAO,YAAY,CAAA;AAG9D,EAAA,IAAI,YAAA,GAAe,CAAA,cAAA,EAAiB,WAAW,CAAA,CAAA,EAAI,WAAW,CAAA,CAAA;AAE9D,EAAA,IAAI,YAAY,QAAA,EAAU;AACxB,IAAA,MAAM,aAAA,GAAgB,WAAA,CAAY,QAAA,CAAS,MAAA,CAAO,CAAC,CAAA,CAAE,WAAA,EAAY,GAAI,WAAA,CAAY,QAAA,CAAS,KAAA,CAAM,CAAC,CAAA;AACjG,IAAA,YAAA,IAAgB,uBAAoB,aAAa,CAAA,CAAA;AAAA,EACnD;AAEA,EAAA,YAAA,IAAgB,sBAAmB,OAAO,CAAA,CAAA;AAE1C,EAAA,IAAI,WAAA,CAAY,IAAA,IAAQ,WAAA,CAAY,IAAA,CAAK,SAAS,CAAA,EAAG;AACnD,IAAA,YAAA,IAAgB,CAAA,gBAAA,EAAgB,WAAA,CAAY,IAAA,CAAK,IAAA,CAAK,IAAI,CAAC,CAAA,CAAA;AAAA,EAC7D;AAGA,EAAA,IAAI,UAAA,GAAa,EAAA;AACjB,EAAA,IAAI,WAAA,CAAY,QAAA,IAAY,WAAA,CAAY,QAAA,EAAU;AAChD,IAAA,MAAM,QAAA,GAAW,YAAY,QAAA,IAAY,KAAA;AACzC,IAAA,MAAM,QAAA,GAAW,YAAY,QAAA,IAAY,KAAA;AACzC,IAAA,UAAA,GAAa;AAAA,gBAAA,EAAqB,QAAQ,uBAAoB,QAAQ,CAAA,CAAA;AAAA,EACxE;AAGA,EAAA,MAAM,eAAA,GAAkB,uDAAA;AAExB,EAAA,IAAI,eAAA,CAAgB,IAAA,CAAK,OAAO,CAAA,EAAG;AAEjC,IAAA,OAAO,OAAA,CAAQ,OAAA,CAAQ,eAAA,EAAiB,YAAA,GAAe,UAAU,CAAA;AAAA,EACnE,CAAA,MAAO;AAEL,IAAA,MAAM,UAAA,GAAa,OAAA,CAAQ,KAAA,CAAM,WAAW,CAAA;AAC5C,IAAA,IAAI,UAAA,EAAY;AACd,MAAA,MAAM,SAAA,GAAY,UAAA,CAAW,KAAA,GAAS,UAAA,CAAW,CAAC,CAAA,CAAE,MAAA;AACpD,MAAA,OAAO,OAAA,CAAQ,KAAA,CAAM,CAAA,EAAG,SAAS,CAAA,GAAI,MAAA,GAAS,YAAA,GAAe,UAAA,GAAa,IAAA,GAAO,OAAA,CAAQ,KAAA,CAAM,SAAS,CAAA;AAAA,IAC1G;AAAA,EACF;AAEA,EAAA,OAAO,OAAA;AACT;AAEA,SAAS,oBAAoB,MAAA,EAAwB;AACnD,EAAA,QAAQ,MAAA;AAAQ,IACd,KAAK,SAAA;AAAW,MAAA,OAAO,iBAAA;AAAA,IACvB,KAAK,aAAA;AAAe,MAAA,OAAO,QAAA;AAAA,IAC3B,KAAK,UAAA;AAAY,MAAA,OAAO,QAAA;AAAA,IACxB,KAAK,UAAA;AAAY,MAAA,OAAO,WAAA;AAAA,IACxB;AAAS,MAAA,OAAO,WAAA;AAAA;AAEpB;AAGA,eAAsB,WAAA,CAAY,OAAA,EAAiB,WAAA,GAAsB,WAAA,EAAqC;AAC5G,EAAA,MAAM,QAAA,GAAgB,IAAA,CAAA,IAAA,CAAK,OAAA,EAAS,WAAW,CAAA;AAE/C,EAAA,IAAI;AACF,IAAA,MAAS,UAAO,QAAQ,CAAA;AACxB,IAAA,OAAO,QAAA;AAAA,EACT,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACF;AAWO,SAAS,aAAA,CAAc,aAA8B,MAAA,EAAoC;AAE9F,EAAA,IAAI,OAAO,MAAA,EAAQ;AACjB,IAAA,MAAM,QAAA,GAAW,KAAA,CAAM,OAAA,CAAQ,MAAA,CAAO,MAAM,IAAI,MAAA,CAAO,MAAA,GAAS,CAAC,MAAA,CAAO,MAAM,CAAA;AAC9E,IAAA,IAAI,CAAC,QAAA,CAAS,QAAA,CAAS,WAAA,CAAY,MAAM,CAAA,EAAG;AAC1C,MAAA,OAAO,KAAA;AAAA,IACT;AAAA,EACF;AAGA,EAAA,IAAI,MAAA,CAAO,IAAA,IAAQ,MAAA,CAAO,IAAA,CAAK,SAAS,CAAA,EAAG;AACzC,IAAA,IAAI,CAAC,WAAA,CAAY,IAAA,IAAQ,WAAA,CAAY,IAAA,CAAK,WAAW,CAAA,EAAG;AACtD,MAAA,OAAO,KAAA;AAAA,IACT;AACA,IAAA,MAAM,UAAA,GAAa,OAAO,IAAA,CAAK,KAAA,CAAM,SAAO,WAAA,CAAY,IAAA,CAAM,QAAA,CAAS,GAAG,CAAC,CAAA;AAC3E,IAAA,IAAI,CAAC,UAAA,EAAY;AACf,MAAA,OAAO,KAAA;AAAA,IACT;AAAA,EACF;AAGA,EAAA,IAAI,OAAO,QAAA,EAAU;AACnB,IAAA,MAAM,UAAA,GAAa,KAAA,CAAM,OAAA,CAAQ,MAAA,CAAO,QAAQ,IAAI,MAAA,CAAO,QAAA,GAAW,CAAC,MAAA,CAAO,QAAQ,CAAA;AACtF,IAAA,IAAI,CAAC,YAAY,QAAA,IAAY,CAAC,WAAW,QAAA,CAAS,WAAA,CAAY,QAAQ,CAAA,EAAG;AACvE,MAAA,OAAO,KAAA;AAAA,IACT;AAAA,EACF;AAGA,EAAA,IAAI,OAAO,QAAA,EAAU;AACnB,IAAA,IAAI,WAAA,CAAY,QAAA,KAAa,MAAA,CAAO,QAAA,EAAU;AAC5C,MAAA,OAAO,KAAA;AAAA,IACT;AAAA,EACF;AAGA,EAAA,IAAI,OAAO,YAAA,EAAc;AACvB,IAAA,KAAA,MAAW,CAAC,KAAK,KAAK,CAAA,IAAK,OAAO,OAAA,CAAQ,MAAA,CAAO,YAAY,CAAA,EAAG;AAC9D,MAAA,IAAI,WAAA,CAAY,GAAG,CAAA,KAAM,KAAA,EAAO;AAC9B,QAAA,OAAO,KAAA;AAAA,MACT;AAAA,IACF;AAAA,EACF;AAEA,EAAA,OAAO,IAAA;AACT","file":"chunk-VN5BUHTV.js","sourcesContent":["import * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport matter from 'gray-matter';\nimport yaml from 'js-yaml';\nimport dayjs from 'dayjs';\nimport type { LeanSpecConfig } from './config.js';\n\n// Valid status values\nexport type SpecStatus = 'planned' | 'in-progress' | 'complete' | 'archived';\n\n// Valid priority values\nexport type SpecPriority = 'low' | 'medium' | 'high' | 'critical';\n\n// Status transition record\nexport interface StatusTransition {\n status: SpecStatus;\n at: string; // ISO 8601 timestamp\n}\n\n// Core frontmatter fields\nexport interface SpecFrontmatter {\n // Required fields\n status: SpecStatus;\n created: string; // YYYY-MM-DD format\n\n // Recommended fields\n tags?: string[];\n priority?: SpecPriority;\n\n // Power user fields\n depends_on?: string[];\n updated?: string;\n completed?: string;\n assignee?: string;\n reviewer?: string;\n issue?: string;\n pr?: string;\n epic?: string;\n breaking?: boolean;\n due?: string; // YYYY-MM-DD format\n\n // Timestamp fields (for velocity tracking)\n created_at?: string; // ISO 8601 timestamp\n updated_at?: string; // ISO 8601 timestamp\n completed_at?: string; // ISO 8601 timestamp\n transitions?: StatusTransition[]; // Status change history\n\n // Allow any additional fields (for extensibility)\n [key: string]: unknown;\n}\n\n/**\n * Convert Date objects to YYYY-MM-DD string format\n * (gray-matter auto-parses YYYY-MM-DD strings as Date objects)\n */\nexport function normalizeDateFields(data: Record<string, unknown>): void {\n const dateFields = ['created', 'completed', 'updated', 'due'];\n \n for (const field of dateFields) {\n if (data[field] instanceof Date) {\n data[field] = (data[field] as Date).toISOString().split('T')[0];\n }\n }\n}\n\n/**\n * Enrich frontmatter with timestamps for velocity tracking\n * Auto-generates timestamps when missing and tracks status transitions\n */\nexport function enrichWithTimestamps(\n data: Record<string, unknown>,\n previousData?: Record<string, unknown>\n): void {\n const now = new Date().toISOString();\n\n // Set created_at if missing - always use current timestamp\n // Do NOT infer from created date field since that's just YYYY-MM-DD without time\n if (!data.created_at) {\n data.created_at = now;\n }\n\n // Update updated_at on any change (if previousData exists)\n if (previousData) {\n data.updated_at = now;\n }\n\n // Set completed_at when status changes to complete\n if (\n data.status === 'complete' &&\n previousData?.status !== 'complete' &&\n !data.completed_at\n ) {\n data.completed_at = now;\n // Also set the completed date field\n if (!data.completed) {\n data.completed = new Date().toISOString().split('T')[0];\n }\n }\n\n // Track status transition (optional)\n if (previousData && data.status !== previousData.status) {\n if (!Array.isArray(data.transitions)) {\n data.transitions = [];\n }\n (data.transitions as StatusTransition[]).push({\n status: data.status as SpecStatus,\n at: now,\n });\n }\n}\n\n/**\n * Normalize tags field - parse JSON strings into arrays\n * Handles cases where AI accidentally creates tags as '[\"..\",\"..\"]' strings\n */\nexport function normalizeTagsField(data: Record<string, unknown>): void {\n if (data.tags && typeof data.tags === 'string') {\n try {\n // Try to parse as JSON array\n const parsed = JSON.parse(data.tags as string);\n if (Array.isArray(parsed)) {\n data.tags = parsed;\n }\n } catch {\n // If not valid JSON, treat as comma-separated string\n data.tags = (data.tags as string).split(',').map(t => t.trim());\n }\n }\n}\n\n/**\n * Validate and coerce custom field types\n */\nexport function validateCustomField(\n value: unknown,\n expectedType: 'string' | 'number' | 'boolean' | 'array'\n): { valid: boolean; coerced?: unknown; error?: string } {\n switch (expectedType) {\n case 'string':\n if (typeof value === 'string') {\n return { valid: true, coerced: value };\n }\n // Coerce to string\n return { valid: true, coerced: String(value) };\n \n case 'number':\n if (typeof value === 'number') {\n return { valid: true, coerced: value };\n }\n // Try to coerce to number\n const num = Number(value);\n if (!isNaN(num)) {\n return { valid: true, coerced: num };\n }\n return { valid: false, error: `Cannot convert '${value}' to number` };\n \n case 'boolean':\n if (typeof value === 'boolean') {\n return { valid: true, coerced: value };\n }\n // Coerce string to boolean\n if (value === 'true' || value === 'yes' || value === '1') {\n return { valid: true, coerced: true };\n }\n if (value === 'false' || value === 'no' || value === '0') {\n return { valid: true, coerced: false };\n }\n return { valid: false, error: `Cannot convert '${value}' to boolean` };\n \n case 'array':\n if (Array.isArray(value)) {\n return { valid: true, coerced: value };\n }\n return { valid: false, error: `Expected array but got ${typeof value}` };\n \n default:\n return { valid: false, error: `Unknown type: ${expectedType}` };\n }\n}\n\n/**\n * Validate custom fields according to config\n */\nexport function validateCustomFields(\n frontmatter: Record<string, unknown>,\n config?: LeanSpecConfig\n): Record<string, unknown> {\n if (!config?.frontmatter?.custom) {\n return frontmatter;\n }\n \n const result = { ...frontmatter };\n \n for (const [fieldName, expectedType] of Object.entries(config.frontmatter.custom)) {\n if (fieldName in result) {\n const validation = validateCustomField(result[fieldName], expectedType);\n if (validation.valid) {\n result[fieldName] = validation.coerced;\n } else {\n console.warn(`Warning: Invalid custom field '${fieldName}': ${validation.error}`);\n }\n }\n }\n \n return result;\n}\n\n// Parse frontmatter from a spec file\nexport async function parseFrontmatter(\n filePath: string,\n config?: LeanSpecConfig\n): Promise<SpecFrontmatter | null> {\n try {\n const content = await fs.readFile(filePath, 'utf-8');\n const parsed = matter(content, {\n engines: {\n yaml: (str) => yaml.load(str, { schema: yaml.FAILSAFE_SCHEMA }) as Record<string, unknown>\n }\n });\n\n if (!parsed.data || Object.keys(parsed.data).length === 0) {\n // No frontmatter found, try fallback to inline fields\n return parseFallbackFields(content);\n }\n\n // Validate required fields\n if (!parsed.data.status) {\n console.warn(`Warning: Missing required field 'status' in ${filePath}`);\n return null;\n }\n\n if (!parsed.data.created) {\n console.warn(`Warning: Missing required field 'created' in ${filePath}`);\n return null;\n }\n\n // Validate status enum\n const validStatuses: SpecStatus[] = ['planned', 'in-progress', 'complete', 'archived'];\n if (!validStatuses.includes(parsed.data.status)) {\n console.warn(`Warning: Invalid status '${parsed.data.status}' in ${filePath}. Valid values: ${validStatuses.join(', ')}`);\n }\n\n // Validate priority enum if present\n if (parsed.data.priority) {\n const validPriorities: SpecPriority[] = ['low', 'medium', 'high', 'critical'];\n if (!validPriorities.includes(parsed.data.priority)) {\n console.warn(`Warning: Invalid priority '${parsed.data.priority}' in ${filePath}. Valid values: ${validPriorities.join(', ')}`);\n }\n }\n\n // Normalize tags field (parse JSON strings to arrays)\n normalizeTagsField(parsed.data);\n \n // Warn about unknown fields (informational only)\n const knownFields = [\n 'status', 'created', 'tags', 'priority', 'depends_on',\n 'updated', 'completed', 'assignee', 'reviewer', 'issue', 'pr', 'epic', 'breaking', 'due',\n 'created_at', 'updated_at', 'completed_at', 'transitions'\n ];\n \n // Add custom fields from config to known fields\n const customFields = config?.frontmatter?.custom ? Object.keys(config.frontmatter.custom) : [];\n const allKnownFields = [...knownFields, ...customFields];\n \n const unknownFields = Object.keys(parsed.data).filter(k => !allKnownFields.includes(k));\n if (unknownFields.length > 0) {\n console.warn(`Info: Unknown fields in ${filePath}: ${unknownFields.join(', ')}`);\n }\n \n // Validate and coerce custom fields\n const validatedData = validateCustomFields(parsed.data, config);\n\n return validatedData as SpecFrontmatter;\n } catch (error) {\n console.error(`Error parsing frontmatter from ${filePath}:`, error);\n return null;\n }\n}\n\n// Fallback: Parse inline fields from older specs\nfunction parseFallbackFields(content: string): SpecFrontmatter | null {\n const statusMatch = content.match(/\\*\\*Status\\*\\*:\\s*(?:📅\\s*)?(\\w+(?:-\\w+)?)/i);\n const createdMatch = content.match(/\\*\\*Created\\*\\*:\\s*(\\d{4}-\\d{2}-\\d{2})/);\n\n if (statusMatch && createdMatch) {\n const status = statusMatch[1].toLowerCase().replace(/\\s+/g, '-') as SpecStatus;\n const created = createdMatch[1];\n\n return {\n status,\n created,\n };\n }\n\n return null;\n}\n\n// Update frontmatter in a spec file\nexport async function updateFrontmatter(\n filePath: string,\n updates: Partial<SpecFrontmatter>\n): Promise<void> {\n const content = await fs.readFile(filePath, 'utf-8');\n const parsed = matter(content, {\n engines: {\n yaml: (str) => yaml.load(str, { schema: yaml.FAILSAFE_SCHEMA }) as Record<string, unknown>\n }\n });\n\n // Store previous data for timestamp enrichment\n const previousData = { ...parsed.data };\n\n // Merge updates with existing data\n const newData = { ...parsed.data, ...updates };\n\n // Ensure date fields remain as strings (gray-matter auto-parses YYYY-MM-DD as Date objects)\n normalizeDateFields(newData);\n\n // Enrich with timestamps\n enrichWithTimestamps(newData, previousData);\n\n // Auto-update timestamps if fields exist (legacy behavior)\n if (updates.status === 'complete' && !newData.completed) {\n newData.completed = dayjs().format('YYYY-MM-DD');\n }\n\n if ('updated' in parsed.data) {\n newData.updated = dayjs().format('YYYY-MM-DD');\n }\n\n // Update visual metadata badges in content\n let updatedContent = parsed.content;\n updatedContent = updateVisualMetadata(updatedContent, newData as SpecFrontmatter);\n\n // Stringify back to file\n const newContent = matter.stringify(updatedContent, newData);\n await fs.writeFile(filePath, newContent, 'utf-8');\n}\n\n// Update visual metadata badges in content\nfunction updateVisualMetadata(content: string, frontmatter: SpecFrontmatter): string {\n // Skip visual metadata update if required fields are missing\n if (!frontmatter.status || !frontmatter.created) {\n return content;\n }\n \n const statusEmoji = getStatusEmojiPlain(frontmatter.status);\n const statusLabel = frontmatter.status.charAt(0).toUpperCase() + frontmatter.status.slice(1).replace('-', ' ');\n \n // Parse created date with dayjs - handles all formats consistently\n const created = dayjs(frontmatter.created).format('YYYY-MM-DD');\n \n // Build metadata line\n let metadataLine = `> **Status**: ${statusEmoji} ${statusLabel}`;\n \n if (frontmatter.priority) {\n const priorityLabel = frontmatter.priority.charAt(0).toUpperCase() + frontmatter.priority.slice(1);\n metadataLine += ` · **Priority**: ${priorityLabel}`;\n }\n \n metadataLine += ` · **Created**: ${created}`;\n \n if (frontmatter.tags && frontmatter.tags.length > 0) {\n metadataLine += ` · **Tags**: ${frontmatter.tags.join(', ')}`;\n }\n \n // For enterprise template with assignee/reviewer\n let secondLine = '';\n if (frontmatter.assignee || frontmatter.reviewer) {\n const assignee = frontmatter.assignee || 'TBD';\n const reviewer = frontmatter.reviewer || 'TBD';\n secondLine = `\\n> **Assignee**: ${assignee} · **Reviewer**: ${reviewer}`;\n }\n \n // Replace existing metadata block or add after title\n const metadataPattern = /^>\\s+\\*\\*Status\\*\\*:.*(?:\\n>\\s+\\*\\*Assignee\\*\\*:.*)?/m;\n \n if (metadataPattern.test(content)) {\n // Replace existing metadata\n return content.replace(metadataPattern, metadataLine + secondLine);\n } else {\n // Add after title (# title)\n const titleMatch = content.match(/^#\\s+.+$/m);\n if (titleMatch) {\n const insertPos = titleMatch.index! + titleMatch[0].length;\n return content.slice(0, insertPos) + '\\n\\n' + metadataLine + secondLine + '\\n' + content.slice(insertPos);\n }\n }\n \n return content;\n}\n\nfunction getStatusEmojiPlain(status: string): string {\n switch (status) {\n case 'planned': return '🗓️';\n case 'in-progress': return '⏳';\n case 'complete': return '✅';\n case 'archived': return '📦';\n default: return '📄';\n }\n}\n\n// Get spec file path from spec directory\nexport async function getSpecFile(specDir: string, defaultFile: string = 'README.md'): Promise<string | null> {\n const specFile = path.join(specDir, defaultFile);\n \n try {\n await fs.access(specFile);\n return specFile;\n } catch {\n return null;\n }\n}\n\n// Filter specs by criteria\nexport interface SpecFilterOptions {\n status?: SpecStatus | SpecStatus[];\n tags?: string[];\n priority?: SpecPriority | SpecPriority[];\n assignee?: string;\n customFields?: Record<string, unknown>;\n}\n\nexport function matchesFilter(frontmatter: SpecFrontmatter, filter: SpecFilterOptions): boolean {\n // Status filter\n if (filter.status) {\n const statuses = Array.isArray(filter.status) ? filter.status : [filter.status];\n if (!statuses.includes(frontmatter.status)) {\n return false;\n }\n }\n\n // Tags filter (spec must have ALL specified tags)\n if (filter.tags && filter.tags.length > 0) {\n if (!frontmatter.tags || frontmatter.tags.length === 0) {\n return false;\n }\n const hasAllTags = filter.tags.every(tag => frontmatter.tags!.includes(tag));\n if (!hasAllTags) {\n return false;\n }\n }\n\n // Priority filter\n if (filter.priority) {\n const priorities = Array.isArray(filter.priority) ? filter.priority : [filter.priority];\n if (!frontmatter.priority || !priorities.includes(frontmatter.priority)) {\n return false;\n }\n }\n\n // Assignee filter\n if (filter.assignee) {\n if (frontmatter.assignee !== filter.assignee) {\n return false;\n }\n }\n \n // Custom fields filter\n if (filter.customFields) {\n for (const [key, value] of Object.entries(filter.customFields)) {\n if (frontmatter[key] !== value) {\n return false;\n }\n }\n }\n\n return true;\n}\n"]}
|
package/dist/cli.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { agentCommand, analyzeCommand, archiveCommand, boardCommand, checkCommand, compactCommand, createCommand, depsCommand, examplesCommand, filesCommand, ganttCommand, initCommand, linkCommand, listCommand, mcpCommand, migrateCommand, openCommand, searchCommand, splitCommand, statsCommand, templatesCommand, timelineCommand, tokensCommand, uiCommand, unlinkCommand, updateCommand, viewCommand } from './chunk-
|
|
2
|
-
import { backfillCommand } from './chunk-
|
|
3
|
-
import { validateCommand } from './chunk-
|
|
4
|
-
import './chunk-
|
|
5
|
-
import './chunk-
|
|
1
|
+
import { agentCommand, analyzeCommand, archiveCommand, boardCommand, checkCommand, compactCommand, createCommand, depsCommand, examplesCommand, filesCommand, ganttCommand, initCommand, linkCommand, listCommand, mcpCommand, migrateCommand, openCommand, searchCommand, splitCommand, statsCommand, templatesCommand, timelineCommand, tokensCommand, uiCommand, unlinkCommand, updateCommand, viewCommand } from './chunk-KTNU4LUR.js';
|
|
2
|
+
import { backfillCommand } from './chunk-H5MCUMBK.js';
|
|
3
|
+
import { validateCommand } from './chunk-CJMVV46H.js';
|
|
4
|
+
import './chunk-RF5PKL6L.js';
|
|
5
|
+
import './chunk-VN5BUHTV.js';
|
|
6
6
|
import { Command } from 'commander';
|
|
7
7
|
import { readFileSync } from 'fs';
|
|
8
8
|
import { fileURLToPath } from 'url';
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { enrichWithTimestamps, getSpecFile, matchesFilter, normalizeDateFields, normalizeTagsField, parseFrontmatter, updateFrontmatter, validateCustomField, validateCustomFields } from './chunk-
|
|
2
|
-
//# sourceMappingURL=frontmatter-
|
|
3
|
-
//# sourceMappingURL=frontmatter-
|
|
1
|
+
export { enrichWithTimestamps, getSpecFile, matchesFilter, normalizeDateFields, normalizeTagsField, parseFrontmatter, updateFrontmatter, validateCustomField, validateCustomFields } from './chunk-VN5BUHTV.js';
|
|
2
|
+
//# sourceMappingURL=frontmatter-6ZBAGOEU.js.map
|
|
3
|
+
//# sourceMappingURL=frontmatter-6ZBAGOEU.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":[],"names":[],"mappings":"","file":"frontmatter-
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"frontmatter-6ZBAGOEU.js"}
|
package/dist/mcp-server.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
export { createMcpServer } from './chunk-
|
|
3
|
-
import './chunk-
|
|
4
|
-
import './chunk-
|
|
5
|
-
import './chunk-
|
|
6
|
-
import './chunk-
|
|
2
|
+
export { createMcpServer } from './chunk-KTNU4LUR.js';
|
|
3
|
+
import './chunk-H5MCUMBK.js';
|
|
4
|
+
import './chunk-CJMVV46H.js';
|
|
5
|
+
import './chunk-RF5PKL6L.js';
|
|
6
|
+
import './chunk-VN5BUHTV.js';
|
|
7
7
|
//# sourceMappingURL=mcp-server.js.map
|
|
8
8
|
//# sourceMappingURL=mcp-server.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":[],"names":[],"mappings":"","file":"validate-
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"validate-DIWYTDEF.js"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lean-spec",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.10",
|
|
4
4
|
"description": "Specification-driven development made simple",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
"zod": "^3.25.76"
|
|
68
68
|
},
|
|
69
69
|
"devDependencies": {
|
|
70
|
-
"@leanspec/core": "^0.2.
|
|
70
|
+
"@leanspec/core": "^0.2.10",
|
|
71
71
|
"@types/js-yaml": "^4.0.9",
|
|
72
72
|
"@types/marked-terminal": "^6.1.1",
|
|
73
73
|
"@types/node": "^20.10.0",
|
|
@@ -12,132 +12,86 @@
|
|
|
12
12
|
|
|
13
13
|
> **Why?** Skipping discovery creates duplicate work. Manual file creation breaks LeanSpec tooling.
|
|
14
14
|
|
|
15
|
-
## 🔧
|
|
16
|
-
|
|
17
|
-
###
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
|
22
|
-
|
|
23
|
-
|
|
|
24
|
-
|
|
|
25
|
-
|
|
|
26
|
-
|
|
|
27
|
-
|
|
|
28
|
-
|
|
|
29
|
-
|
|
|
30
|
-
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
```bash
|
|
44
|
-
lean-spec board # Project overview
|
|
45
|
-
lean-spec list # See all specs
|
|
46
|
-
lean-spec search "query" # Find relevant specs
|
|
47
|
-
lean-spec create <name> # Create new spec
|
|
48
|
-
lean-spec update <spec> --status <status> # Update status
|
|
49
|
-
lean-spec link <spec> --depends-on <other> # Add dependencies
|
|
50
|
-
lean-spec unlink <spec> --depends-on <other> # Remove dependencies
|
|
51
|
-
lean-spec deps <spec> # Show dependencies
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
**Tip:** Check if you have LeanSpec MCP tools available before using CLI.
|
|
55
|
-
|
|
56
|
-
## ⚠️ SDD Workflow Checkpoints
|
|
57
|
-
|
|
58
|
-
### Before Starting ANY Task
|
|
59
|
-
|
|
60
|
-
1. 📋 **Run `board`** - What's the current project state?
|
|
61
|
-
2. 🔍 **Run `search`** - Are there related specs already?
|
|
62
|
-
3. 📝 **Check existing specs** - Is there one for this work?
|
|
63
|
-
|
|
64
|
-
### During Implementation
|
|
65
|
-
|
|
66
|
-
4. 📊 **Update status to `in-progress`** BEFORE coding
|
|
67
|
-
5. 📝 **Document decisions** in the spec as you work
|
|
68
|
-
6. 🔗 **Link dependencies** if you discover blocking relationships
|
|
69
|
-
|
|
70
|
-
### After Completing Work
|
|
71
|
-
|
|
72
|
-
7. ✅ **Update status to `complete`** when done
|
|
73
|
-
8. 📄 **Document what you learned** in the spec
|
|
74
|
-
9. 🤔 **Create follow-up specs** if needed
|
|
75
|
-
|
|
76
|
-
### 🚫 Common Mistakes to Avoid
|
|
15
|
+
## 🔧 Managing Specs
|
|
16
|
+
|
|
17
|
+
### MCP Tools (Preferred) with CLI Fallback
|
|
18
|
+
|
|
19
|
+
| Action | MCP Tool | CLI Fallback |
|
|
20
|
+
|--------|----------|--------------|
|
|
21
|
+
| Project status | `board` | `lean-spec board` |
|
|
22
|
+
| List specs | `list` | `lean-spec list` |
|
|
23
|
+
| Search specs | `search` | `lean-spec search "query"` |
|
|
24
|
+
| View spec | `view` | `lean-spec view <spec>` |
|
|
25
|
+
| Create spec | `create` | `lean-spec create <name>` |
|
|
26
|
+
| Update spec | `update` | `lean-spec update <spec> --status <status>` |
|
|
27
|
+
| Link specs | `link` | `lean-spec link <spec> --depends-on <other>` |
|
|
28
|
+
| Unlink specs | `unlink` | `lean-spec unlink <spec> --depends-on <other>` |
|
|
29
|
+
| Dependencies | `deps` | `lean-spec deps <spec>` |
|
|
30
|
+
| Token count | `tokens` | `lean-spec tokens <spec>` |
|
|
31
|
+
|
|
32
|
+
## ⚠️ Core Rules
|
|
33
|
+
|
|
34
|
+
| Rule | Details |
|
|
35
|
+
|------|---------|
|
|
36
|
+
| **NEVER edit frontmatter manually** | Use `update`, `link`, `unlink` for: `status`, `priority`, `tags`, `assignee`, `transitions`, timestamps, `depends_on` |
|
|
37
|
+
| **ALWAYS link spec references** | Content mentions another spec → `lean-spec link <spec> --depends-on <other>` |
|
|
38
|
+
| **Track status transitions** | `planned` → `in-progress` (before coding) → `complete` (after done) |
|
|
39
|
+
| **No nested code blocks** | Use indentation instead |
|
|
40
|
+
|
|
41
|
+
### 🚫 Common Mistakes
|
|
77
42
|
|
|
78
43
|
| ❌ Don't | ✅ Do Instead |
|
|
79
44
|
|----------|---------------|
|
|
80
45
|
| Create spec files manually | Use `create` tool |
|
|
81
|
-
| Skip discovery
|
|
82
|
-
| Leave status as "planned"
|
|
83
|
-
| Finish work without updating spec | Document decisions, update status |
|
|
46
|
+
| Skip discovery | Run `board` and `search` first |
|
|
47
|
+
| Leave status as "planned" | Update to `in-progress` before coding |
|
|
84
48
|
| Edit frontmatter manually | Use `update` tool |
|
|
85
|
-
| Forget about specs mid-conversation | Check spec status periodically |
|
|
86
|
-
|
|
87
|
-
## Core Rules
|
|
88
49
|
|
|
89
|
-
|
|
90
|
-
2. **Check specs/** - Review existing specs before starting
|
|
91
|
-
3. **Use MCP tools** - Prefer MCP over CLI when available
|
|
92
|
-
4. **Follow LeanSpec principles** - Clarity over documentation
|
|
93
|
-
5. **Keep it minimal** - If it doesn't add clarity, cut it
|
|
94
|
-
6. **NEVER manually edit frontmatter** - Use `update`, `link`, `unlink` tools
|
|
95
|
-
7. **Track progress in specs** - Update status and document decisions
|
|
50
|
+
## 📋 SDD Workflow
|
|
96
51
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
- Design decisions needing team alignment
|
|
52
|
+
```
|
|
53
|
+
BEFORE: board → search → check existing specs
|
|
54
|
+
DURING: update status to in-progress → code → document decisions → link dependencies
|
|
55
|
+
AFTER: update status to complete → document learnings
|
|
56
|
+
```
|
|
103
57
|
|
|
104
|
-
**
|
|
105
|
-
- Bug fixes
|
|
106
|
-
- Trivial changes
|
|
107
|
-
- Self-explanatory refactors
|
|
58
|
+
**Status tracks implementation, NOT spec writing.**
|
|
108
59
|
|
|
109
60
|
## Spec Dependencies
|
|
110
61
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
**Use when:** True blocking dependency, work order matters, one spec builds on another.
|
|
62
|
+
Use `depends_on` to express blocking relationships between specs:
|
|
63
|
+
- **`depends_on`** = True blocker, work order matters, directional (A depends on B)
|
|
114
64
|
|
|
65
|
+
Link dependencies when one spec builds on another:
|
|
115
66
|
```bash
|
|
116
67
|
lean-spec link <spec> --depends-on <other-spec>
|
|
117
68
|
```
|
|
118
69
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
## Quality Standards
|
|
70
|
+
## When to Use Specs
|
|
122
71
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
- Never leave specs with stale status
|
|
72
|
+
| ✅ Write spec | ❌ Skip spec |
|
|
73
|
+
|---------------|--------------|
|
|
74
|
+
| Multi-part features | Bug fixes |
|
|
75
|
+
| Breaking changes | Trivial changes |
|
|
76
|
+
| Design decisions | Self-explanatory refactors |
|
|
129
77
|
|
|
130
|
-
##
|
|
78
|
+
## Token Thresholds
|
|
131
79
|
|
|
132
80
|
| Tokens | Status |
|
|
133
81
|
|--------|--------|
|
|
134
82
|
| <2,000 | ✅ Optimal |
|
|
135
83
|
| 2,000-3,500 | ✅ Good |
|
|
136
84
|
| 3,500-5,000 | ⚠️ Consider splitting |
|
|
137
|
-
| >5,000 | 🔴
|
|
85
|
+
| >5,000 | 🔴 Must split |
|
|
86
|
+
|
|
87
|
+
## First Principles (Priority Order)
|
|
138
88
|
|
|
139
|
-
|
|
89
|
+
1. **Context Economy** - <2,000 tokens optimal, >3,500 needs splitting
|
|
90
|
+
2. **Signal-to-Noise** - Every word must inform a decision
|
|
91
|
+
3. **Intent Over Implementation** - Capture why, let how emerge
|
|
92
|
+
4. **Bridge the Gap** - Both human and AI must understand
|
|
93
|
+
5. **Progressive Disclosure** - Add complexity only when pain is felt
|
|
140
94
|
|
|
141
95
|
---
|
|
142
96
|
|
|
143
|
-
**Remember:** LeanSpec tracks what you're building. Keep specs in sync with your work!
|
|
97
|
+
**Remember:** LeanSpec tracks what you're building. Keep specs in sync with your work!
|