@saga-ai/cli 0.1.3 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +21 -0
  2. package/dist/cli.cjs +1597 -50
  3. package/package.json +12 -12
package/dist/cli.cjs CHANGED
@@ -3037,8 +3037,8 @@ var {
3037
3037
  } = import_index.default;
3038
3038
 
3039
3039
  // src/cli.ts
3040
- var import_node_path4 = require("node:path");
3041
- var import_node_fs4 = require("node:fs");
3040
+ var import_node_path5 = require("node:path");
3041
+ var import_node_fs5 = require("node:fs");
3042
3042
 
3043
3043
  // src/commands/init.ts
3044
3044
  var import_node_path2 = require("node:path");
@@ -3206,8 +3206,1546 @@ async function initCommand(options) {
3206
3206
 
3207
3207
  // src/commands/implement.ts
3208
3208
  var import_node_child_process = require("node:child_process");
3209
- var import_node_path3 = require("node:path");
3209
+ var import_node_path4 = require("node:path");
3210
+ var import_node_fs4 = require("node:fs");
3211
+
3212
+ // src/utils/finder.ts
3210
3213
  var import_node_fs3 = require("node:fs");
3214
+ var import_node_path3 = require("node:path");
3215
+
3216
+ // node_modules/.pnpm/fuse.js@7.1.0/node_modules/fuse.js/dist/fuse.mjs
3217
+ function isArray(value) {
3218
+ return !Array.isArray ? getTag(value) === "[object Array]" : Array.isArray(value);
3219
+ }
3220
+ var INFINITY = 1 / 0;
3221
+ function baseToString(value) {
3222
+ if (typeof value == "string") {
3223
+ return value;
3224
+ }
3225
+ let result = value + "";
3226
+ return result == "0" && 1 / value == -INFINITY ? "-0" : result;
3227
+ }
3228
+ function toString(value) {
3229
+ return value == null ? "" : baseToString(value);
3230
+ }
3231
+ function isString(value) {
3232
+ return typeof value === "string";
3233
+ }
3234
+ function isNumber(value) {
3235
+ return typeof value === "number";
3236
+ }
3237
+ function isBoolean(value) {
3238
+ return value === true || value === false || isObjectLike(value) && getTag(value) == "[object Boolean]";
3239
+ }
3240
+ function isObject(value) {
3241
+ return typeof value === "object";
3242
+ }
3243
+ function isObjectLike(value) {
3244
+ return isObject(value) && value !== null;
3245
+ }
3246
+ function isDefined(value) {
3247
+ return value !== void 0 && value !== null;
3248
+ }
3249
+ function isBlank(value) {
3250
+ return !value.trim().length;
3251
+ }
3252
+ function getTag(value) {
3253
+ return value == null ? value === void 0 ? "[object Undefined]" : "[object Null]" : Object.prototype.toString.call(value);
3254
+ }
3255
+ var INCORRECT_INDEX_TYPE = "Incorrect 'index' type";
3256
+ var LOGICAL_SEARCH_INVALID_QUERY_FOR_KEY = (key) => `Invalid value for key ${key}`;
3257
+ var PATTERN_LENGTH_TOO_LARGE = (max) => `Pattern length exceeds max of ${max}.`;
3258
+ var MISSING_KEY_PROPERTY = (name) => `Missing ${name} property in key`;
3259
+ var INVALID_KEY_WEIGHT_VALUE = (key) => `Property 'weight' in key '${key}' must be a positive integer`;
3260
+ var hasOwn = Object.prototype.hasOwnProperty;
3261
+ var KeyStore = class {
3262
+ constructor(keys) {
3263
+ this._keys = [];
3264
+ this._keyMap = {};
3265
+ let totalWeight = 0;
3266
+ keys.forEach((key) => {
3267
+ let obj = createKey(key);
3268
+ this._keys.push(obj);
3269
+ this._keyMap[obj.id] = obj;
3270
+ totalWeight += obj.weight;
3271
+ });
3272
+ this._keys.forEach((key) => {
3273
+ key.weight /= totalWeight;
3274
+ });
3275
+ }
3276
+ get(keyId) {
3277
+ return this._keyMap[keyId];
3278
+ }
3279
+ keys() {
3280
+ return this._keys;
3281
+ }
3282
+ toJSON() {
3283
+ return JSON.stringify(this._keys);
3284
+ }
3285
+ };
3286
+ function createKey(key) {
3287
+ let path = null;
3288
+ let id = null;
3289
+ let src = null;
3290
+ let weight = 1;
3291
+ let getFn = null;
3292
+ if (isString(key) || isArray(key)) {
3293
+ src = key;
3294
+ path = createKeyPath(key);
3295
+ id = createKeyId(key);
3296
+ } else {
3297
+ if (!hasOwn.call(key, "name")) {
3298
+ throw new Error(MISSING_KEY_PROPERTY("name"));
3299
+ }
3300
+ const name = key.name;
3301
+ src = name;
3302
+ if (hasOwn.call(key, "weight")) {
3303
+ weight = key.weight;
3304
+ if (weight <= 0) {
3305
+ throw new Error(INVALID_KEY_WEIGHT_VALUE(name));
3306
+ }
3307
+ }
3308
+ path = createKeyPath(name);
3309
+ id = createKeyId(name);
3310
+ getFn = key.getFn;
3311
+ }
3312
+ return { path, id, weight, src, getFn };
3313
+ }
3314
+ function createKeyPath(key) {
3315
+ return isArray(key) ? key : key.split(".");
3316
+ }
3317
+ function createKeyId(key) {
3318
+ return isArray(key) ? key.join(".") : key;
3319
+ }
3320
+ function get(obj, path) {
3321
+ let list = [];
3322
+ let arr = false;
3323
+ const deepGet = (obj2, path2, index) => {
3324
+ if (!isDefined(obj2)) {
3325
+ return;
3326
+ }
3327
+ if (!path2[index]) {
3328
+ list.push(obj2);
3329
+ } else {
3330
+ let key = path2[index];
3331
+ const value = obj2[key];
3332
+ if (!isDefined(value)) {
3333
+ return;
3334
+ }
3335
+ if (index === path2.length - 1 && (isString(value) || isNumber(value) || isBoolean(value))) {
3336
+ list.push(toString(value));
3337
+ } else if (isArray(value)) {
3338
+ arr = true;
3339
+ for (let i = 0, len = value.length; i < len; i += 1) {
3340
+ deepGet(value[i], path2, index + 1);
3341
+ }
3342
+ } else if (path2.length) {
3343
+ deepGet(value, path2, index + 1);
3344
+ }
3345
+ }
3346
+ };
3347
+ deepGet(obj, isString(path) ? path.split(".") : path, 0);
3348
+ return arr ? list : list[0];
3349
+ }
3350
+ var MatchOptions = {
3351
+ // Whether the matches should be included in the result set. When `true`, each record in the result
3352
+ // set will include the indices of the matched characters.
3353
+ // These can consequently be used for highlighting purposes.
3354
+ includeMatches: false,
3355
+ // When `true`, the matching function will continue to the end of a search pattern even if
3356
+ // a perfect match has already been located in the string.
3357
+ findAllMatches: false,
3358
+ // Minimum number of characters that must be matched before a result is considered a match
3359
+ minMatchCharLength: 1
3360
+ };
3361
+ var BasicOptions = {
3362
+ // When `true`, the algorithm continues searching to the end of the input even if a perfect
3363
+ // match is found before the end of the same input.
3364
+ isCaseSensitive: false,
3365
+ // When `true`, the algorithm will ignore diacritics (accents) in comparisons
3366
+ ignoreDiacritics: false,
3367
+ // When true, the matching function will continue to the end of a search pattern even if
3368
+ includeScore: false,
3369
+ // List of properties that will be searched. This also supports nested properties.
3370
+ keys: [],
3371
+ // Whether to sort the result list, by score
3372
+ shouldSort: true,
3373
+ // Default sort function: sort by ascending score, ascending index
3374
+ sortFn: (a, b) => a.score === b.score ? a.idx < b.idx ? -1 : 1 : a.score < b.score ? -1 : 1
3375
+ };
3376
+ var FuzzyOptions = {
3377
+ // Approximately where in the text is the pattern expected to be found?
3378
+ location: 0,
3379
+ // At what point does the match algorithm give up. A threshold of '0.0' requires a perfect match
3380
+ // (of both letters and location), a threshold of '1.0' would match anything.
3381
+ threshold: 0.6,
3382
+ // Determines how close the match must be to the fuzzy location (specified above).
3383
+ // An exact letter match which is 'distance' characters away from the fuzzy location
3384
+ // would score as a complete mismatch. A distance of '0' requires the match be at
3385
+ // the exact location specified, a threshold of '1000' would require a perfect match
3386
+ // to be within 800 characters of the fuzzy location to be found using a 0.8 threshold.
3387
+ distance: 100
3388
+ };
3389
+ var AdvancedOptions = {
3390
+ // When `true`, it enables the use of unix-like search commands
3391
+ useExtendedSearch: false,
3392
+ // The get function to use when fetching an object's properties.
3393
+ // The default will search nested paths *ie foo.bar.baz*
3394
+ getFn: get,
3395
+ // When `true`, search will ignore `location` and `distance`, so it won't matter
3396
+ // where in the string the pattern appears.
3397
+ // More info: https://fusejs.io/concepts/scoring-theory.html#fuzziness-score
3398
+ ignoreLocation: false,
3399
+ // When `true`, the calculation for the relevance score (used for sorting) will
3400
+ // ignore the field-length norm.
3401
+ // More info: https://fusejs.io/concepts/scoring-theory.html#field-length-norm
3402
+ ignoreFieldNorm: false,
3403
+ // The weight to determine how much field length norm effects scoring.
3404
+ fieldNormWeight: 1
3405
+ };
3406
+ var Config = {
3407
+ ...BasicOptions,
3408
+ ...MatchOptions,
3409
+ ...FuzzyOptions,
3410
+ ...AdvancedOptions
3411
+ };
3412
+ var SPACE = /[^ ]+/g;
3413
+ function norm(weight = 1, mantissa = 3) {
3414
+ const cache = /* @__PURE__ */ new Map();
3415
+ const m = Math.pow(10, mantissa);
3416
+ return {
3417
+ get(value) {
3418
+ const numTokens = value.match(SPACE).length;
3419
+ if (cache.has(numTokens)) {
3420
+ return cache.get(numTokens);
3421
+ }
3422
+ const norm2 = 1 / Math.pow(numTokens, 0.5 * weight);
3423
+ const n = parseFloat(Math.round(norm2 * m) / m);
3424
+ cache.set(numTokens, n);
3425
+ return n;
3426
+ },
3427
+ clear() {
3428
+ cache.clear();
3429
+ }
3430
+ };
3431
+ }
3432
+ var FuseIndex = class {
3433
+ constructor({
3434
+ getFn = Config.getFn,
3435
+ fieldNormWeight = Config.fieldNormWeight
3436
+ } = {}) {
3437
+ this.norm = norm(fieldNormWeight, 3);
3438
+ this.getFn = getFn;
3439
+ this.isCreated = false;
3440
+ this.setIndexRecords();
3441
+ }
3442
+ setSources(docs = []) {
3443
+ this.docs = docs;
3444
+ }
3445
+ setIndexRecords(records = []) {
3446
+ this.records = records;
3447
+ }
3448
+ setKeys(keys = []) {
3449
+ this.keys = keys;
3450
+ this._keysMap = {};
3451
+ keys.forEach((key, idx) => {
3452
+ this._keysMap[key.id] = idx;
3453
+ });
3454
+ }
3455
+ create() {
3456
+ if (this.isCreated || !this.docs.length) {
3457
+ return;
3458
+ }
3459
+ this.isCreated = true;
3460
+ if (isString(this.docs[0])) {
3461
+ this.docs.forEach((doc, docIndex) => {
3462
+ this._addString(doc, docIndex);
3463
+ });
3464
+ } else {
3465
+ this.docs.forEach((doc, docIndex) => {
3466
+ this._addObject(doc, docIndex);
3467
+ });
3468
+ }
3469
+ this.norm.clear();
3470
+ }
3471
+ // Adds a doc to the end of the index
3472
+ add(doc) {
3473
+ const idx = this.size();
3474
+ if (isString(doc)) {
3475
+ this._addString(doc, idx);
3476
+ } else {
3477
+ this._addObject(doc, idx);
3478
+ }
3479
+ }
3480
+ // Removes the doc at the specified index of the index
3481
+ removeAt(idx) {
3482
+ this.records.splice(idx, 1);
3483
+ for (let i = idx, len = this.size(); i < len; i += 1) {
3484
+ this.records[i].i -= 1;
3485
+ }
3486
+ }
3487
+ getValueForItemAtKeyId(item, keyId) {
3488
+ return item[this._keysMap[keyId]];
3489
+ }
3490
+ size() {
3491
+ return this.records.length;
3492
+ }
3493
+ _addString(doc, docIndex) {
3494
+ if (!isDefined(doc) || isBlank(doc)) {
3495
+ return;
3496
+ }
3497
+ let record = {
3498
+ v: doc,
3499
+ i: docIndex,
3500
+ n: this.norm.get(doc)
3501
+ };
3502
+ this.records.push(record);
3503
+ }
3504
+ _addObject(doc, docIndex) {
3505
+ let record = { i: docIndex, $: {} };
3506
+ this.keys.forEach((key, keyIndex) => {
3507
+ let value = key.getFn ? key.getFn(doc) : this.getFn(doc, key.path);
3508
+ if (!isDefined(value)) {
3509
+ return;
3510
+ }
3511
+ if (isArray(value)) {
3512
+ let subRecords = [];
3513
+ const stack = [{ nestedArrIndex: -1, value }];
3514
+ while (stack.length) {
3515
+ const { nestedArrIndex, value: value2 } = stack.pop();
3516
+ if (!isDefined(value2)) {
3517
+ continue;
3518
+ }
3519
+ if (isString(value2) && !isBlank(value2)) {
3520
+ let subRecord = {
3521
+ v: value2,
3522
+ i: nestedArrIndex,
3523
+ n: this.norm.get(value2)
3524
+ };
3525
+ subRecords.push(subRecord);
3526
+ } else if (isArray(value2)) {
3527
+ value2.forEach((item, k) => {
3528
+ stack.push({
3529
+ nestedArrIndex: k,
3530
+ value: item
3531
+ });
3532
+ });
3533
+ } else ;
3534
+ }
3535
+ record.$[keyIndex] = subRecords;
3536
+ } else if (isString(value) && !isBlank(value)) {
3537
+ let subRecord = {
3538
+ v: value,
3539
+ n: this.norm.get(value)
3540
+ };
3541
+ record.$[keyIndex] = subRecord;
3542
+ }
3543
+ });
3544
+ this.records.push(record);
3545
+ }
3546
+ toJSON() {
3547
+ return {
3548
+ keys: this.keys,
3549
+ records: this.records
3550
+ };
3551
+ }
3552
+ };
3553
+ function createIndex(keys, docs, { getFn = Config.getFn, fieldNormWeight = Config.fieldNormWeight } = {}) {
3554
+ const myIndex = new FuseIndex({ getFn, fieldNormWeight });
3555
+ myIndex.setKeys(keys.map(createKey));
3556
+ myIndex.setSources(docs);
3557
+ myIndex.create();
3558
+ return myIndex;
3559
+ }
3560
+ function parseIndex(data, { getFn = Config.getFn, fieldNormWeight = Config.fieldNormWeight } = {}) {
3561
+ const { keys, records } = data;
3562
+ const myIndex = new FuseIndex({ getFn, fieldNormWeight });
3563
+ myIndex.setKeys(keys);
3564
+ myIndex.setIndexRecords(records);
3565
+ return myIndex;
3566
+ }
3567
+ function computeScore$1(pattern, {
3568
+ errors = 0,
3569
+ currentLocation = 0,
3570
+ expectedLocation = 0,
3571
+ distance = Config.distance,
3572
+ ignoreLocation = Config.ignoreLocation
3573
+ } = {}) {
3574
+ const accuracy = errors / pattern.length;
3575
+ if (ignoreLocation) {
3576
+ return accuracy;
3577
+ }
3578
+ const proximity = Math.abs(expectedLocation - currentLocation);
3579
+ if (!distance) {
3580
+ return proximity ? 1 : accuracy;
3581
+ }
3582
+ return accuracy + proximity / distance;
3583
+ }
3584
+ function convertMaskToIndices(matchmask = [], minMatchCharLength = Config.minMatchCharLength) {
3585
+ let indices = [];
3586
+ let start = -1;
3587
+ let end = -1;
3588
+ let i = 0;
3589
+ for (let len = matchmask.length; i < len; i += 1) {
3590
+ let match = matchmask[i];
3591
+ if (match && start === -1) {
3592
+ start = i;
3593
+ } else if (!match && start !== -1) {
3594
+ end = i - 1;
3595
+ if (end - start + 1 >= minMatchCharLength) {
3596
+ indices.push([start, end]);
3597
+ }
3598
+ start = -1;
3599
+ }
3600
+ }
3601
+ if (matchmask[i - 1] && i - start >= minMatchCharLength) {
3602
+ indices.push([start, i - 1]);
3603
+ }
3604
+ return indices;
3605
+ }
3606
+ var MAX_BITS = 32;
3607
+ function search(text, pattern, patternAlphabet, {
3608
+ location = Config.location,
3609
+ distance = Config.distance,
3610
+ threshold = Config.threshold,
3611
+ findAllMatches = Config.findAllMatches,
3612
+ minMatchCharLength = Config.minMatchCharLength,
3613
+ includeMatches = Config.includeMatches,
3614
+ ignoreLocation = Config.ignoreLocation
3615
+ } = {}) {
3616
+ if (pattern.length > MAX_BITS) {
3617
+ throw new Error(PATTERN_LENGTH_TOO_LARGE(MAX_BITS));
3618
+ }
3619
+ const patternLen = pattern.length;
3620
+ const textLen = text.length;
3621
+ const expectedLocation = Math.max(0, Math.min(location, textLen));
3622
+ let currentThreshold = threshold;
3623
+ let bestLocation = expectedLocation;
3624
+ const computeMatches = minMatchCharLength > 1 || includeMatches;
3625
+ const matchMask = computeMatches ? Array(textLen) : [];
3626
+ let index;
3627
+ while ((index = text.indexOf(pattern, bestLocation)) > -1) {
3628
+ let score = computeScore$1(pattern, {
3629
+ currentLocation: index,
3630
+ expectedLocation,
3631
+ distance,
3632
+ ignoreLocation
3633
+ });
3634
+ currentThreshold = Math.min(score, currentThreshold);
3635
+ bestLocation = index + patternLen;
3636
+ if (computeMatches) {
3637
+ let i = 0;
3638
+ while (i < patternLen) {
3639
+ matchMask[index + i] = 1;
3640
+ i += 1;
3641
+ }
3642
+ }
3643
+ }
3644
+ bestLocation = -1;
3645
+ let lastBitArr = [];
3646
+ let finalScore = 1;
3647
+ let binMax = patternLen + textLen;
3648
+ const mask = 1 << patternLen - 1;
3649
+ for (let i = 0; i < patternLen; i += 1) {
3650
+ let binMin = 0;
3651
+ let binMid = binMax;
3652
+ while (binMin < binMid) {
3653
+ const score2 = computeScore$1(pattern, {
3654
+ errors: i,
3655
+ currentLocation: expectedLocation + binMid,
3656
+ expectedLocation,
3657
+ distance,
3658
+ ignoreLocation
3659
+ });
3660
+ if (score2 <= currentThreshold) {
3661
+ binMin = binMid;
3662
+ } else {
3663
+ binMax = binMid;
3664
+ }
3665
+ binMid = Math.floor((binMax - binMin) / 2 + binMin);
3666
+ }
3667
+ binMax = binMid;
3668
+ let start = Math.max(1, expectedLocation - binMid + 1);
3669
+ let finish = findAllMatches ? textLen : Math.min(expectedLocation + binMid, textLen) + patternLen;
3670
+ let bitArr = Array(finish + 2);
3671
+ bitArr[finish + 1] = (1 << i) - 1;
3672
+ for (let j = finish; j >= start; j -= 1) {
3673
+ let currentLocation = j - 1;
3674
+ let charMatch = patternAlphabet[text.charAt(currentLocation)];
3675
+ if (computeMatches) {
3676
+ matchMask[currentLocation] = +!!charMatch;
3677
+ }
3678
+ bitArr[j] = (bitArr[j + 1] << 1 | 1) & charMatch;
3679
+ if (i) {
3680
+ bitArr[j] |= (lastBitArr[j + 1] | lastBitArr[j]) << 1 | 1 | lastBitArr[j + 1];
3681
+ }
3682
+ if (bitArr[j] & mask) {
3683
+ finalScore = computeScore$1(pattern, {
3684
+ errors: i,
3685
+ currentLocation,
3686
+ expectedLocation,
3687
+ distance,
3688
+ ignoreLocation
3689
+ });
3690
+ if (finalScore <= currentThreshold) {
3691
+ currentThreshold = finalScore;
3692
+ bestLocation = currentLocation;
3693
+ if (bestLocation <= expectedLocation) {
3694
+ break;
3695
+ }
3696
+ start = Math.max(1, 2 * expectedLocation - bestLocation);
3697
+ }
3698
+ }
3699
+ }
3700
+ const score = computeScore$1(pattern, {
3701
+ errors: i + 1,
3702
+ currentLocation: expectedLocation,
3703
+ expectedLocation,
3704
+ distance,
3705
+ ignoreLocation
3706
+ });
3707
+ if (score > currentThreshold) {
3708
+ break;
3709
+ }
3710
+ lastBitArr = bitArr;
3711
+ }
3712
+ const result = {
3713
+ isMatch: bestLocation >= 0,
3714
+ // Count exact matches (those with a score of 0) to be "almost" exact
3715
+ score: Math.max(1e-3, finalScore)
3716
+ };
3717
+ if (computeMatches) {
3718
+ const indices = convertMaskToIndices(matchMask, minMatchCharLength);
3719
+ if (!indices.length) {
3720
+ result.isMatch = false;
3721
+ } else if (includeMatches) {
3722
+ result.indices = indices;
3723
+ }
3724
+ }
3725
+ return result;
3726
+ }
3727
+ function createPatternAlphabet(pattern) {
3728
+ let mask = {};
3729
+ for (let i = 0, len = pattern.length; i < len; i += 1) {
3730
+ const char = pattern.charAt(i);
3731
+ mask[char] = (mask[char] || 0) | 1 << len - i - 1;
3732
+ }
3733
+ return mask;
3734
+ }
3735
+ var stripDiacritics = String.prototype.normalize ? (str) => str.normalize("NFD").replace(/[\u0300-\u036F\u0483-\u0489\u0591-\u05BD\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7\u0610-\u061A\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7\u06E8\u06EA-\u06ED\u0711\u0730-\u074A\u07A6-\u07B0\u07EB-\u07F3\u07FD\u0816-\u0819\u081B-\u0823\u0825-\u0827\u0829-\u082D\u0859-\u085B\u08D3-\u08E1\u08E3-\u0903\u093A-\u093C\u093E-\u094F\u0951-\u0957\u0962\u0963\u0981-\u0983\u09BC\u09BE-\u09C4\u09C7\u09C8\u09CB-\u09CD\u09D7\u09E2\u09E3\u09FE\u0A01-\u0A03\u0A3C\u0A3E-\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A70\u0A71\u0A75\u0A81-\u0A83\u0ABC\u0ABE-\u0AC5\u0AC7-\u0AC9\u0ACB-\u0ACD\u0AE2\u0AE3\u0AFA-\u0AFF\u0B01-\u0B03\u0B3C\u0B3E-\u0B44\u0B47\u0B48\u0B4B-\u0B4D\u0B56\u0B57\u0B62\u0B63\u0B82\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD7\u0C00-\u0C04\u0C3E-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C62\u0C63\u0C81-\u0C83\u0CBC\u0CBE-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD\u0CD5\u0CD6\u0CE2\u0CE3\u0D00-\u0D03\u0D3B\u0D3C\u0D3E-\u0D44\u0D46-\u0D48\u0D4A-\u0D4D\u0D57\u0D62\u0D63\u0D82\u0D83\u0DCA\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DF2\u0DF3\u0E31\u0E34-\u0E3A\u0E47-\u0E4E\u0EB1\u0EB4-\u0EB9\u0EBB\u0EBC\u0EC8-\u0ECD\u0F18\u0F19\u0F35\u0F37\u0F39\u0F3E\u0F3F\u0F71-\u0F84\u0F86\u0F87\u0F8D-\u0F97\u0F99-\u0FBC\u0FC6\u102B-\u103E\u1056-\u1059\u105E-\u1060\u1062-\u1064\u1067-\u106D\u1071-\u1074\u1082-\u108D\u108F\u109A-\u109D\u135D-\u135F\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17B4-\u17D3\u17DD\u180B-\u180D\u1885\u1886\u18A9\u1920-\u192B\u1930-\u193B\u1A17-\u1A1B\u1A55-\u1A5E\u1A60-\u1A7C\u1A7F\u1AB0-\u1ABE\u1B00-\u1B04\u1B34-\u1B44\u1B6B-\u1B73\u1B80-\u1B82\u1BA1-\u1BAD\u1BE6-\u1BF3\u1C24-\u1C37\u1CD0-\u1CD2\u1CD4-\u1CE8\u1CED\u1CF2-\u1CF4\u1CF7-\u1CF9\u1DC0-\u1DF9\u1DFB-\u1DFF\u20D0-\u20F0\u2CEF-\u2CF1\u2D7F\u2DE0-\u2DFF\u302A-\u302F\u3099\u309A\uA66F-\uA672\uA674-\uA67D\uA69E\uA69F\uA6F0\uA6F1\uA802\uA806\uA80B\uA823-\uA827\uA880\uA881\uA8B4-\uA8C5\uA8E0-\uA8F1\uA8FF\uA926-\uA92D\uA947-\uA953\uA980-\uA983\uA9B3-\uA9C0\uA9E5\uAA29-\uAA36\uAA43\uAA4C\uAA4D\uAA7B-\uAA7D\uAAB0\uAAB2-\uAAB4\uAAB7\uAAB8\uAABE\uAABF\uAAC1\uAAEB-\uAAEF\uAAF5\uAAF6\uABE3-\uABEA\uABEC\uABED\uFB1E\uFE00-\uFE0F\uFE20-\uFE2F]/g, "") : (str) => str;
3736
+ var BitapSearch = class {
3737
+ constructor(pattern, {
3738
+ location = Config.location,
3739
+ threshold = Config.threshold,
3740
+ distance = Config.distance,
3741
+ includeMatches = Config.includeMatches,
3742
+ findAllMatches = Config.findAllMatches,
3743
+ minMatchCharLength = Config.minMatchCharLength,
3744
+ isCaseSensitive = Config.isCaseSensitive,
3745
+ ignoreDiacritics = Config.ignoreDiacritics,
3746
+ ignoreLocation = Config.ignoreLocation
3747
+ } = {}) {
3748
+ this.options = {
3749
+ location,
3750
+ threshold,
3751
+ distance,
3752
+ includeMatches,
3753
+ findAllMatches,
3754
+ minMatchCharLength,
3755
+ isCaseSensitive,
3756
+ ignoreDiacritics,
3757
+ ignoreLocation
3758
+ };
3759
+ pattern = isCaseSensitive ? pattern : pattern.toLowerCase();
3760
+ pattern = ignoreDiacritics ? stripDiacritics(pattern) : pattern;
3761
+ this.pattern = pattern;
3762
+ this.chunks = [];
3763
+ if (!this.pattern.length) {
3764
+ return;
3765
+ }
3766
+ const addChunk = (pattern2, startIndex) => {
3767
+ this.chunks.push({
3768
+ pattern: pattern2,
3769
+ alphabet: createPatternAlphabet(pattern2),
3770
+ startIndex
3771
+ });
3772
+ };
3773
+ const len = this.pattern.length;
3774
+ if (len > MAX_BITS) {
3775
+ let i = 0;
3776
+ const remainder = len % MAX_BITS;
3777
+ const end = len - remainder;
3778
+ while (i < end) {
3779
+ addChunk(this.pattern.substr(i, MAX_BITS), i);
3780
+ i += MAX_BITS;
3781
+ }
3782
+ if (remainder) {
3783
+ const startIndex = len - MAX_BITS;
3784
+ addChunk(this.pattern.substr(startIndex), startIndex);
3785
+ }
3786
+ } else {
3787
+ addChunk(this.pattern, 0);
3788
+ }
3789
+ }
3790
+ searchIn(text) {
3791
+ const { isCaseSensitive, ignoreDiacritics, includeMatches } = this.options;
3792
+ text = isCaseSensitive ? text : text.toLowerCase();
3793
+ text = ignoreDiacritics ? stripDiacritics(text) : text;
3794
+ if (this.pattern === text) {
3795
+ let result2 = {
3796
+ isMatch: true,
3797
+ score: 0
3798
+ };
3799
+ if (includeMatches) {
3800
+ result2.indices = [[0, text.length - 1]];
3801
+ }
3802
+ return result2;
3803
+ }
3804
+ const {
3805
+ location,
3806
+ distance,
3807
+ threshold,
3808
+ findAllMatches,
3809
+ minMatchCharLength,
3810
+ ignoreLocation
3811
+ } = this.options;
3812
+ let allIndices = [];
3813
+ let totalScore = 0;
3814
+ let hasMatches = false;
3815
+ this.chunks.forEach(({ pattern, alphabet, startIndex }) => {
3816
+ const { isMatch, score, indices } = search(text, pattern, alphabet, {
3817
+ location: location + startIndex,
3818
+ distance,
3819
+ threshold,
3820
+ findAllMatches,
3821
+ minMatchCharLength,
3822
+ includeMatches,
3823
+ ignoreLocation
3824
+ });
3825
+ if (isMatch) {
3826
+ hasMatches = true;
3827
+ }
3828
+ totalScore += score;
3829
+ if (isMatch && indices) {
3830
+ allIndices = [...allIndices, ...indices];
3831
+ }
3832
+ });
3833
+ let result = {
3834
+ isMatch: hasMatches,
3835
+ score: hasMatches ? totalScore / this.chunks.length : 1
3836
+ };
3837
+ if (hasMatches && includeMatches) {
3838
+ result.indices = allIndices;
3839
+ }
3840
+ return result;
3841
+ }
3842
+ };
3843
+ var BaseMatch = class {
3844
+ constructor(pattern) {
3845
+ this.pattern = pattern;
3846
+ }
3847
+ static isMultiMatch(pattern) {
3848
+ return getMatch(pattern, this.multiRegex);
3849
+ }
3850
+ static isSingleMatch(pattern) {
3851
+ return getMatch(pattern, this.singleRegex);
3852
+ }
3853
+ search() {
3854
+ }
3855
+ };
3856
+ function getMatch(pattern, exp) {
3857
+ const matches = pattern.match(exp);
3858
+ return matches ? matches[1] : null;
3859
+ }
3860
+ var ExactMatch = class extends BaseMatch {
3861
+ constructor(pattern) {
3862
+ super(pattern);
3863
+ }
3864
+ static get type() {
3865
+ return "exact";
3866
+ }
3867
+ static get multiRegex() {
3868
+ return /^="(.*)"$/;
3869
+ }
3870
+ static get singleRegex() {
3871
+ return /^=(.*)$/;
3872
+ }
3873
+ search(text) {
3874
+ const isMatch = text === this.pattern;
3875
+ return {
3876
+ isMatch,
3877
+ score: isMatch ? 0 : 1,
3878
+ indices: [0, this.pattern.length - 1]
3879
+ };
3880
+ }
3881
+ };
3882
+ var InverseExactMatch = class extends BaseMatch {
3883
+ constructor(pattern) {
3884
+ super(pattern);
3885
+ }
3886
+ static get type() {
3887
+ return "inverse-exact";
3888
+ }
3889
+ static get multiRegex() {
3890
+ return /^!"(.*)"$/;
3891
+ }
3892
+ static get singleRegex() {
3893
+ return /^!(.*)$/;
3894
+ }
3895
+ search(text) {
3896
+ const index = text.indexOf(this.pattern);
3897
+ const isMatch = index === -1;
3898
+ return {
3899
+ isMatch,
3900
+ score: isMatch ? 0 : 1,
3901
+ indices: [0, text.length - 1]
3902
+ };
3903
+ }
3904
+ };
3905
+ var PrefixExactMatch = class extends BaseMatch {
3906
+ constructor(pattern) {
3907
+ super(pattern);
3908
+ }
3909
+ static get type() {
3910
+ return "prefix-exact";
3911
+ }
3912
+ static get multiRegex() {
3913
+ return /^\^"(.*)"$/;
3914
+ }
3915
+ static get singleRegex() {
3916
+ return /^\^(.*)$/;
3917
+ }
3918
+ search(text) {
3919
+ const isMatch = text.startsWith(this.pattern);
3920
+ return {
3921
+ isMatch,
3922
+ score: isMatch ? 0 : 1,
3923
+ indices: [0, this.pattern.length - 1]
3924
+ };
3925
+ }
3926
+ };
3927
+ var InversePrefixExactMatch = class extends BaseMatch {
3928
+ constructor(pattern) {
3929
+ super(pattern);
3930
+ }
3931
+ static get type() {
3932
+ return "inverse-prefix-exact";
3933
+ }
3934
+ static get multiRegex() {
3935
+ return /^!\^"(.*)"$/;
3936
+ }
3937
+ static get singleRegex() {
3938
+ return /^!\^(.*)$/;
3939
+ }
3940
+ search(text) {
3941
+ const isMatch = !text.startsWith(this.pattern);
3942
+ return {
3943
+ isMatch,
3944
+ score: isMatch ? 0 : 1,
3945
+ indices: [0, text.length - 1]
3946
+ };
3947
+ }
3948
+ };
3949
+ var SuffixExactMatch = class extends BaseMatch {
3950
+ constructor(pattern) {
3951
+ super(pattern);
3952
+ }
3953
+ static get type() {
3954
+ return "suffix-exact";
3955
+ }
3956
+ static get multiRegex() {
3957
+ return /^"(.*)"\$$/;
3958
+ }
3959
+ static get singleRegex() {
3960
+ return /^(.*)\$$/;
3961
+ }
3962
+ search(text) {
3963
+ const isMatch = text.endsWith(this.pattern);
3964
+ return {
3965
+ isMatch,
3966
+ score: isMatch ? 0 : 1,
3967
+ indices: [text.length - this.pattern.length, text.length - 1]
3968
+ };
3969
+ }
3970
+ };
3971
+ var InverseSuffixExactMatch = class extends BaseMatch {
3972
+ constructor(pattern) {
3973
+ super(pattern);
3974
+ }
3975
+ static get type() {
3976
+ return "inverse-suffix-exact";
3977
+ }
3978
+ static get multiRegex() {
3979
+ return /^!"(.*)"\$$/;
3980
+ }
3981
+ static get singleRegex() {
3982
+ return /^!(.*)\$$/;
3983
+ }
3984
+ search(text) {
3985
+ const isMatch = !text.endsWith(this.pattern);
3986
+ return {
3987
+ isMatch,
3988
+ score: isMatch ? 0 : 1,
3989
+ indices: [0, text.length - 1]
3990
+ };
3991
+ }
3992
+ };
3993
+ var FuzzyMatch = class extends BaseMatch {
3994
+ constructor(pattern, {
3995
+ location = Config.location,
3996
+ threshold = Config.threshold,
3997
+ distance = Config.distance,
3998
+ includeMatches = Config.includeMatches,
3999
+ findAllMatches = Config.findAllMatches,
4000
+ minMatchCharLength = Config.minMatchCharLength,
4001
+ isCaseSensitive = Config.isCaseSensitive,
4002
+ ignoreDiacritics = Config.ignoreDiacritics,
4003
+ ignoreLocation = Config.ignoreLocation
4004
+ } = {}) {
4005
+ super(pattern);
4006
+ this._bitapSearch = new BitapSearch(pattern, {
4007
+ location,
4008
+ threshold,
4009
+ distance,
4010
+ includeMatches,
4011
+ findAllMatches,
4012
+ minMatchCharLength,
4013
+ isCaseSensitive,
4014
+ ignoreDiacritics,
4015
+ ignoreLocation
4016
+ });
4017
+ }
4018
+ static get type() {
4019
+ return "fuzzy";
4020
+ }
4021
+ static get multiRegex() {
4022
+ return /^"(.*)"$/;
4023
+ }
4024
+ static get singleRegex() {
4025
+ return /^(.*)$/;
4026
+ }
4027
+ search(text) {
4028
+ return this._bitapSearch.searchIn(text);
4029
+ }
4030
+ };
4031
+ var IncludeMatch = class extends BaseMatch {
4032
+ constructor(pattern) {
4033
+ super(pattern);
4034
+ }
4035
+ static get type() {
4036
+ return "include";
4037
+ }
4038
+ static get multiRegex() {
4039
+ return /^'"(.*)"$/;
4040
+ }
4041
+ static get singleRegex() {
4042
+ return /^'(.*)$/;
4043
+ }
4044
+ search(text) {
4045
+ let location = 0;
4046
+ let index;
4047
+ const indices = [];
4048
+ const patternLen = this.pattern.length;
4049
+ while ((index = text.indexOf(this.pattern, location)) > -1) {
4050
+ location = index + patternLen;
4051
+ indices.push([index, location - 1]);
4052
+ }
4053
+ const isMatch = !!indices.length;
4054
+ return {
4055
+ isMatch,
4056
+ score: isMatch ? 0 : 1,
4057
+ indices
4058
+ };
4059
+ }
4060
+ };
4061
+ var searchers = [
4062
+ ExactMatch,
4063
+ IncludeMatch,
4064
+ PrefixExactMatch,
4065
+ InversePrefixExactMatch,
4066
+ InverseSuffixExactMatch,
4067
+ SuffixExactMatch,
4068
+ InverseExactMatch,
4069
+ FuzzyMatch
4070
+ ];
4071
+ var searchersLen = searchers.length;
4072
+ var SPACE_RE = / +(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)/;
4073
+ var OR_TOKEN = "|";
4074
+ function parseQuery(pattern, options = {}) {
4075
+ return pattern.split(OR_TOKEN).map((item) => {
4076
+ let query = item.trim().split(SPACE_RE).filter((item2) => item2 && !!item2.trim());
4077
+ let results = [];
4078
+ for (let i = 0, len = query.length; i < len; i += 1) {
4079
+ const queryItem = query[i];
4080
+ let found = false;
4081
+ let idx = -1;
4082
+ while (!found && ++idx < searchersLen) {
4083
+ const searcher = searchers[idx];
4084
+ let token = searcher.isMultiMatch(queryItem);
4085
+ if (token) {
4086
+ results.push(new searcher(token, options));
4087
+ found = true;
4088
+ }
4089
+ }
4090
+ if (found) {
4091
+ continue;
4092
+ }
4093
+ idx = -1;
4094
+ while (++idx < searchersLen) {
4095
+ const searcher = searchers[idx];
4096
+ let token = searcher.isSingleMatch(queryItem);
4097
+ if (token) {
4098
+ results.push(new searcher(token, options));
4099
+ break;
4100
+ }
4101
+ }
4102
+ }
4103
+ return results;
4104
+ });
4105
+ }
4106
+ var MultiMatchSet = /* @__PURE__ */ new Set([FuzzyMatch.type, IncludeMatch.type]);
4107
+ var ExtendedSearch = class {
4108
+ constructor(pattern, {
4109
+ isCaseSensitive = Config.isCaseSensitive,
4110
+ ignoreDiacritics = Config.ignoreDiacritics,
4111
+ includeMatches = Config.includeMatches,
4112
+ minMatchCharLength = Config.minMatchCharLength,
4113
+ ignoreLocation = Config.ignoreLocation,
4114
+ findAllMatches = Config.findAllMatches,
4115
+ location = Config.location,
4116
+ threshold = Config.threshold,
4117
+ distance = Config.distance
4118
+ } = {}) {
4119
+ this.query = null;
4120
+ this.options = {
4121
+ isCaseSensitive,
4122
+ ignoreDiacritics,
4123
+ includeMatches,
4124
+ minMatchCharLength,
4125
+ findAllMatches,
4126
+ ignoreLocation,
4127
+ location,
4128
+ threshold,
4129
+ distance
4130
+ };
4131
+ pattern = isCaseSensitive ? pattern : pattern.toLowerCase();
4132
+ pattern = ignoreDiacritics ? stripDiacritics(pattern) : pattern;
4133
+ this.pattern = pattern;
4134
+ this.query = parseQuery(this.pattern, this.options);
4135
+ }
4136
+ static condition(_, options) {
4137
+ return options.useExtendedSearch;
4138
+ }
4139
+ searchIn(text) {
4140
+ const query = this.query;
4141
+ if (!query) {
4142
+ return {
4143
+ isMatch: false,
4144
+ score: 1
4145
+ };
4146
+ }
4147
+ const { includeMatches, isCaseSensitive, ignoreDiacritics } = this.options;
4148
+ text = isCaseSensitive ? text : text.toLowerCase();
4149
+ text = ignoreDiacritics ? stripDiacritics(text) : text;
4150
+ let numMatches = 0;
4151
+ let allIndices = [];
4152
+ let totalScore = 0;
4153
+ for (let i = 0, qLen = query.length; i < qLen; i += 1) {
4154
+ const searchers2 = query[i];
4155
+ allIndices.length = 0;
4156
+ numMatches = 0;
4157
+ for (let j = 0, pLen = searchers2.length; j < pLen; j += 1) {
4158
+ const searcher = searchers2[j];
4159
+ const { isMatch, indices, score } = searcher.search(text);
4160
+ if (isMatch) {
4161
+ numMatches += 1;
4162
+ totalScore += score;
4163
+ if (includeMatches) {
4164
+ const type = searcher.constructor.type;
4165
+ if (MultiMatchSet.has(type)) {
4166
+ allIndices = [...allIndices, ...indices];
4167
+ } else {
4168
+ allIndices.push(indices);
4169
+ }
4170
+ }
4171
+ } else {
4172
+ totalScore = 0;
4173
+ numMatches = 0;
4174
+ allIndices.length = 0;
4175
+ break;
4176
+ }
4177
+ }
4178
+ if (numMatches) {
4179
+ let result = {
4180
+ isMatch: true,
4181
+ score: totalScore / numMatches
4182
+ };
4183
+ if (includeMatches) {
4184
+ result.indices = allIndices;
4185
+ }
4186
+ return result;
4187
+ }
4188
+ }
4189
+ return {
4190
+ isMatch: false,
4191
+ score: 1
4192
+ };
4193
+ }
4194
+ };
4195
+ var registeredSearchers = [];
4196
+ function register(...args) {
4197
+ registeredSearchers.push(...args);
4198
+ }
4199
+ function createSearcher(pattern, options) {
4200
+ for (let i = 0, len = registeredSearchers.length; i < len; i += 1) {
4201
+ let searcherClass = registeredSearchers[i];
4202
+ if (searcherClass.condition(pattern, options)) {
4203
+ return new searcherClass(pattern, options);
4204
+ }
4205
+ }
4206
+ return new BitapSearch(pattern, options);
4207
+ }
4208
+ var LogicalOperator = {
4209
+ AND: "$and",
4210
+ OR: "$or"
4211
+ };
4212
+ var KeyType = {
4213
+ PATH: "$path",
4214
+ PATTERN: "$val"
4215
+ };
4216
+ var isExpression = (query) => !!(query[LogicalOperator.AND] || query[LogicalOperator.OR]);
4217
+ var isPath = (query) => !!query[KeyType.PATH];
4218
+ var isLeaf = (query) => !isArray(query) && isObject(query) && !isExpression(query);
4219
+ var convertToExplicit = (query) => ({
4220
+ [LogicalOperator.AND]: Object.keys(query).map((key) => ({
4221
+ [key]: query[key]
4222
+ }))
4223
+ });
4224
+ function parse2(query, options, { auto = true } = {}) {
4225
+ const next = (query2) => {
4226
+ let keys = Object.keys(query2);
4227
+ const isQueryPath = isPath(query2);
4228
+ if (!isQueryPath && keys.length > 1 && !isExpression(query2)) {
4229
+ return next(convertToExplicit(query2));
4230
+ }
4231
+ if (isLeaf(query2)) {
4232
+ const key = isQueryPath ? query2[KeyType.PATH] : keys[0];
4233
+ const pattern = isQueryPath ? query2[KeyType.PATTERN] : query2[key];
4234
+ if (!isString(pattern)) {
4235
+ throw new Error(LOGICAL_SEARCH_INVALID_QUERY_FOR_KEY(key));
4236
+ }
4237
+ const obj = {
4238
+ keyId: createKeyId(key),
4239
+ pattern
4240
+ };
4241
+ if (auto) {
4242
+ obj.searcher = createSearcher(pattern, options);
4243
+ }
4244
+ return obj;
4245
+ }
4246
+ let node = {
4247
+ children: [],
4248
+ operator: keys[0]
4249
+ };
4250
+ keys.forEach((key) => {
4251
+ const value = query2[key];
4252
+ if (isArray(value)) {
4253
+ value.forEach((item) => {
4254
+ node.children.push(next(item));
4255
+ });
4256
+ }
4257
+ });
4258
+ return node;
4259
+ };
4260
+ if (!isExpression(query)) {
4261
+ query = convertToExplicit(query);
4262
+ }
4263
+ return next(query);
4264
+ }
4265
+ function computeScore(results, { ignoreFieldNorm = Config.ignoreFieldNorm }) {
4266
+ results.forEach((result) => {
4267
+ let totalScore = 1;
4268
+ result.matches.forEach(({ key, norm: norm2, score }) => {
4269
+ const weight = key ? key.weight : null;
4270
+ totalScore *= Math.pow(
4271
+ score === 0 && weight ? Number.EPSILON : score,
4272
+ (weight || 1) * (ignoreFieldNorm ? 1 : norm2)
4273
+ );
4274
+ });
4275
+ result.score = totalScore;
4276
+ });
4277
+ }
4278
+ function transformMatches(result, data) {
4279
+ const matches = result.matches;
4280
+ data.matches = [];
4281
+ if (!isDefined(matches)) {
4282
+ return;
4283
+ }
4284
+ matches.forEach((match) => {
4285
+ if (!isDefined(match.indices) || !match.indices.length) {
4286
+ return;
4287
+ }
4288
+ const { indices, value } = match;
4289
+ let obj = {
4290
+ indices,
4291
+ value
4292
+ };
4293
+ if (match.key) {
4294
+ obj.key = match.key.src;
4295
+ }
4296
+ if (match.idx > -1) {
4297
+ obj.refIndex = match.idx;
4298
+ }
4299
+ data.matches.push(obj);
4300
+ });
4301
+ }
4302
+ function transformScore(result, data) {
4303
+ data.score = result.score;
4304
+ }
4305
+ function format(results, docs, {
4306
+ includeMatches = Config.includeMatches,
4307
+ includeScore = Config.includeScore
4308
+ } = {}) {
4309
+ const transformers = [];
4310
+ if (includeMatches) transformers.push(transformMatches);
4311
+ if (includeScore) transformers.push(transformScore);
4312
+ return results.map((result) => {
4313
+ const { idx } = result;
4314
+ const data = {
4315
+ item: docs[idx],
4316
+ refIndex: idx
4317
+ };
4318
+ if (transformers.length) {
4319
+ transformers.forEach((transformer) => {
4320
+ transformer(result, data);
4321
+ });
4322
+ }
4323
+ return data;
4324
+ });
4325
+ }
4326
+ var Fuse = class {
4327
+ constructor(docs, options = {}, index) {
4328
+ this.options = { ...Config, ...options };
4329
+ if (this.options.useExtendedSearch && false) {
4330
+ throw new Error(EXTENDED_SEARCH_UNAVAILABLE);
4331
+ }
4332
+ this._keyStore = new KeyStore(this.options.keys);
4333
+ this.setCollection(docs, index);
4334
+ }
4335
+ setCollection(docs, index) {
4336
+ this._docs = docs;
4337
+ if (index && !(index instanceof FuseIndex)) {
4338
+ throw new Error(INCORRECT_INDEX_TYPE);
4339
+ }
4340
+ this._myIndex = index || createIndex(this.options.keys, this._docs, {
4341
+ getFn: this.options.getFn,
4342
+ fieldNormWeight: this.options.fieldNormWeight
4343
+ });
4344
+ }
4345
+ add(doc) {
4346
+ if (!isDefined(doc)) {
4347
+ return;
4348
+ }
4349
+ this._docs.push(doc);
4350
+ this._myIndex.add(doc);
4351
+ }
4352
+ remove(predicate = () => false) {
4353
+ const results = [];
4354
+ for (let i = 0, len = this._docs.length; i < len; i += 1) {
4355
+ const doc = this._docs[i];
4356
+ if (predicate(doc, i)) {
4357
+ this.removeAt(i);
4358
+ i -= 1;
4359
+ len -= 1;
4360
+ results.push(doc);
4361
+ }
4362
+ }
4363
+ return results;
4364
+ }
4365
+ removeAt(idx) {
4366
+ this._docs.splice(idx, 1);
4367
+ this._myIndex.removeAt(idx);
4368
+ }
4369
+ getIndex() {
4370
+ return this._myIndex;
4371
+ }
4372
+ search(query, { limit = -1 } = {}) {
4373
+ const {
4374
+ includeMatches,
4375
+ includeScore,
4376
+ shouldSort,
4377
+ sortFn,
4378
+ ignoreFieldNorm
4379
+ } = this.options;
4380
+ let results = isString(query) ? isString(this._docs[0]) ? this._searchStringList(query) : this._searchObjectList(query) : this._searchLogical(query);
4381
+ computeScore(results, { ignoreFieldNorm });
4382
+ if (shouldSort) {
4383
+ results.sort(sortFn);
4384
+ }
4385
+ if (isNumber(limit) && limit > -1) {
4386
+ results = results.slice(0, limit);
4387
+ }
4388
+ return format(results, this._docs, {
4389
+ includeMatches,
4390
+ includeScore
4391
+ });
4392
+ }
4393
+ _searchStringList(query) {
4394
+ const searcher = createSearcher(query, this.options);
4395
+ const { records } = this._myIndex;
4396
+ const results = [];
4397
+ records.forEach(({ v: text, i: idx, n: norm2 }) => {
4398
+ if (!isDefined(text)) {
4399
+ return;
4400
+ }
4401
+ const { isMatch, score, indices } = searcher.searchIn(text);
4402
+ if (isMatch) {
4403
+ results.push({
4404
+ item: text,
4405
+ idx,
4406
+ matches: [{ score, value: text, norm: norm2, indices }]
4407
+ });
4408
+ }
4409
+ });
4410
+ return results;
4411
+ }
4412
+ _searchLogical(query) {
4413
+ const expression = parse2(query, this.options);
4414
+ const evaluate = (node, item, idx) => {
4415
+ if (!node.children) {
4416
+ const { keyId, searcher } = node;
4417
+ const matches = this._findMatches({
4418
+ key: this._keyStore.get(keyId),
4419
+ value: this._myIndex.getValueForItemAtKeyId(item, keyId),
4420
+ searcher
4421
+ });
4422
+ if (matches && matches.length) {
4423
+ return [
4424
+ {
4425
+ idx,
4426
+ item,
4427
+ matches
4428
+ }
4429
+ ];
4430
+ }
4431
+ return [];
4432
+ }
4433
+ const res = [];
4434
+ for (let i = 0, len = node.children.length; i < len; i += 1) {
4435
+ const child = node.children[i];
4436
+ const result = evaluate(child, item, idx);
4437
+ if (result.length) {
4438
+ res.push(...result);
4439
+ } else if (node.operator === LogicalOperator.AND) {
4440
+ return [];
4441
+ }
4442
+ }
4443
+ return res;
4444
+ };
4445
+ const records = this._myIndex.records;
4446
+ const resultMap = {};
4447
+ const results = [];
4448
+ records.forEach(({ $: item, i: idx }) => {
4449
+ if (isDefined(item)) {
4450
+ let expResults = evaluate(expression, item, idx);
4451
+ if (expResults.length) {
4452
+ if (!resultMap[idx]) {
4453
+ resultMap[idx] = { idx, item, matches: [] };
4454
+ results.push(resultMap[idx]);
4455
+ }
4456
+ expResults.forEach(({ matches }) => {
4457
+ resultMap[idx].matches.push(...matches);
4458
+ });
4459
+ }
4460
+ }
4461
+ });
4462
+ return results;
4463
+ }
4464
+ _searchObjectList(query) {
4465
+ const searcher = createSearcher(query, this.options);
4466
+ const { keys, records } = this._myIndex;
4467
+ const results = [];
4468
+ records.forEach(({ $: item, i: idx }) => {
4469
+ if (!isDefined(item)) {
4470
+ return;
4471
+ }
4472
+ let matches = [];
4473
+ keys.forEach((key, keyIndex) => {
4474
+ matches.push(
4475
+ ...this._findMatches({
4476
+ key,
4477
+ value: item[keyIndex],
4478
+ searcher
4479
+ })
4480
+ );
4481
+ });
4482
+ if (matches.length) {
4483
+ results.push({
4484
+ idx,
4485
+ item,
4486
+ matches
4487
+ });
4488
+ }
4489
+ });
4490
+ return results;
4491
+ }
4492
+ _findMatches({ key, value, searcher }) {
4493
+ if (!isDefined(value)) {
4494
+ return [];
4495
+ }
4496
+ let matches = [];
4497
+ if (isArray(value)) {
4498
+ value.forEach(({ v: text, i: idx, n: norm2 }) => {
4499
+ if (!isDefined(text)) {
4500
+ return;
4501
+ }
4502
+ const { isMatch, score, indices } = searcher.searchIn(text);
4503
+ if (isMatch) {
4504
+ matches.push({
4505
+ score,
4506
+ key,
4507
+ value: text,
4508
+ idx,
4509
+ norm: norm2,
4510
+ indices
4511
+ });
4512
+ }
4513
+ });
4514
+ } else {
4515
+ const { v: text, n: norm2 } = value;
4516
+ const { isMatch, score, indices } = searcher.searchIn(text);
4517
+ if (isMatch) {
4518
+ matches.push({ score, key, value: text, norm: norm2, indices });
4519
+ }
4520
+ }
4521
+ return matches;
4522
+ }
4523
+ };
4524
+ Fuse.version = "7.1.0";
4525
+ Fuse.createIndex = createIndex;
4526
+ Fuse.parseIndex = parseIndex;
4527
+ Fuse.config = Config;
4528
+ {
4529
+ Fuse.parseQuery = parse2;
4530
+ }
4531
+ {
4532
+ register(ExtendedSearch);
4533
+ }
4534
+
4535
+ // src/utils/finder.ts
4536
+ var FUZZY_THRESHOLD = 0.3;
4537
+ var MATCH_THRESHOLD = 0.6;
4538
+ var SCORE_SIMILARITY_THRESHOLD = 0.1;
4539
+ function parseFrontmatter(content) {
4540
+ if (!content || !content.startsWith("---")) {
4541
+ return { frontmatter: {}, body: content };
4542
+ }
4543
+ const endIndex = content.indexOf("\n---", 3);
4544
+ if (endIndex === -1) {
4545
+ return { frontmatter: {}, body: content };
4546
+ }
4547
+ const frontmatterBlock = content.slice(4, endIndex);
4548
+ const body = content.slice(endIndex + 4).trim();
4549
+ const frontmatter = {};
4550
+ for (const line of frontmatterBlock.split("\n")) {
4551
+ const trimmed = line.trim();
4552
+ if (!trimmed || trimmed.startsWith("#")) continue;
4553
+ const colonIndex = trimmed.indexOf(":");
4554
+ if (colonIndex === -1) continue;
4555
+ const key = trimmed.slice(0, colonIndex).trim();
4556
+ let value = trimmed.slice(colonIndex + 1).trim();
4557
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
4558
+ value = value.slice(1, -1);
4559
+ }
4560
+ frontmatter[key] = value;
4561
+ }
4562
+ return { frontmatter, body };
4563
+ }
4564
+ function extractContext(body, maxLength = 300) {
4565
+ const contextMatch = body.match(/##\s*Context\s*\n+([\s\S]*?)(?=\n##|\Z|$)/i);
4566
+ if (!contextMatch) {
4567
+ return "";
4568
+ }
4569
+ let context = contextMatch[1].trim();
4570
+ if (context.length > maxLength) {
4571
+ return context.slice(0, maxLength - 3) + "...";
4572
+ }
4573
+ return context;
4574
+ }
4575
+ function normalize(str) {
4576
+ return str.toLowerCase().replace(/[-_]/g, " ");
4577
+ }
4578
+ function findEpic(projectPath, query) {
4579
+ const epicsDir = (0, import_node_path3.join)(projectPath, ".saga", "epics");
4580
+ if (!(0, import_node_fs3.existsSync)(epicsDir)) {
4581
+ return {
4582
+ found: false,
4583
+ error: "No .saga/epics/ directory found"
4584
+ };
4585
+ }
4586
+ const epicSlugs = (0, import_node_fs3.readdirSync)(epicsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
4587
+ if (epicSlugs.length === 0) {
4588
+ return {
4589
+ found: false,
4590
+ error: `No epic found matching '${query}'`
4591
+ };
4592
+ }
4593
+ const queryNormalized = query.toLowerCase().replace(/_/g, "-");
4594
+ for (const slug of epicSlugs) {
4595
+ if (slug.toLowerCase() === queryNormalized) {
4596
+ return {
4597
+ found: true,
4598
+ data: { slug }
4599
+ };
4600
+ }
4601
+ }
4602
+ const epics = epicSlugs.map((slug) => ({ slug }));
4603
+ const fuse = new Fuse(epics, {
4604
+ keys: ["slug"],
4605
+ threshold: MATCH_THRESHOLD,
4606
+ includeScore: true
4607
+ });
4608
+ const results = fuse.search(query);
4609
+ if (results.length === 0) {
4610
+ return {
4611
+ found: false,
4612
+ error: `No epic found matching '${query}'`
4613
+ };
4614
+ }
4615
+ if (results.length === 1) {
4616
+ return {
4617
+ found: true,
4618
+ data: results[0].item
4619
+ };
4620
+ }
4621
+ const bestScore = results[0].score ?? 0;
4622
+ const similarMatches = results.filter(
4623
+ (r) => (r.score ?? 0) - bestScore <= SCORE_SIMILARITY_THRESHOLD
4624
+ );
4625
+ if (similarMatches.length > 1) {
4626
+ return {
4627
+ found: false,
4628
+ matches: similarMatches.map((r) => r.item)
4629
+ };
4630
+ }
4631
+ if (bestScore <= FUZZY_THRESHOLD) {
4632
+ return {
4633
+ found: true,
4634
+ data: results[0].item
4635
+ };
4636
+ }
4637
+ return {
4638
+ found: false,
4639
+ matches: results.map((r) => r.item)
4640
+ };
4641
+ }
4642
+ function findStory(projectPath, query) {
4643
+ const worktreesDir = (0, import_node_path3.join)(projectPath, ".saga", "worktrees");
4644
+ if (!(0, import_node_fs3.existsSync)(worktreesDir)) {
4645
+ return {
4646
+ found: false,
4647
+ error: "No .saga/worktrees/ directory found. Run /generate-stories first."
4648
+ };
4649
+ }
4650
+ const allStories = [];
4651
+ const epicDirs = (0, import_node_fs3.readdirSync)(worktreesDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
4652
+ for (const epicSlug of epicDirs) {
4653
+ const epicWorktreeDir = (0, import_node_path3.join)(worktreesDir, epicSlug);
4654
+ const storyDirs = (0, import_node_fs3.readdirSync)(epicWorktreeDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
4655
+ for (const storySlugFromDir of storyDirs) {
4656
+ const worktreePath = (0, import_node_path3.join)(epicWorktreeDir, storySlugFromDir);
4657
+ const storyPath = (0, import_node_path3.join)(
4658
+ worktreePath,
4659
+ ".saga",
4660
+ "epics",
4661
+ epicSlug,
4662
+ "stories",
4663
+ storySlugFromDir,
4664
+ "story.md"
4665
+ );
4666
+ if (!(0, import_node_fs3.existsSync)(storyPath)) {
4667
+ continue;
4668
+ }
4669
+ try {
4670
+ const content = (0, import_node_fs3.readFileSync)(storyPath, "utf-8");
4671
+ const { frontmatter, body } = parseFrontmatter(content);
4672
+ const storySlug = frontmatter.id || frontmatter.slug || storySlugFromDir;
4673
+ const title = frontmatter.title || "";
4674
+ const status = frontmatter.status || "";
4675
+ allStories.push({
4676
+ slug: storySlug,
4677
+ title,
4678
+ status,
4679
+ context: extractContext(body),
4680
+ epicSlug,
4681
+ storyPath,
4682
+ worktreePath
4683
+ });
4684
+ } catch {
4685
+ continue;
4686
+ }
4687
+ }
4688
+ }
4689
+ if (allStories.length === 0) {
4690
+ return {
4691
+ found: false,
4692
+ error: `No story found matching '${query}'`
4693
+ };
4694
+ }
4695
+ const queryNormalized = normalize(query);
4696
+ for (const story of allStories) {
4697
+ if (normalize(story.slug) === queryNormalized) {
4698
+ return {
4699
+ found: true,
4700
+ data: story
4701
+ };
4702
+ }
4703
+ }
4704
+ const fuse = new Fuse(allStories, {
4705
+ keys: [
4706
+ { name: "slug", weight: 2 },
4707
+ // Prioritize slug matches
4708
+ { name: "title", weight: 1 }
4709
+ ],
4710
+ threshold: MATCH_THRESHOLD,
4711
+ includeScore: true
4712
+ });
4713
+ const results = fuse.search(query);
4714
+ if (results.length === 0) {
4715
+ return {
4716
+ found: false,
4717
+ error: `No story found matching '${query}'`
4718
+ };
4719
+ }
4720
+ if (results.length === 1) {
4721
+ return {
4722
+ found: true,
4723
+ data: results[0].item
4724
+ };
4725
+ }
4726
+ const bestScore = results[0].score ?? 0;
4727
+ const similarMatches = results.filter(
4728
+ (r) => (r.score ?? 0) - bestScore <= SCORE_SIMILARITY_THRESHOLD
4729
+ );
4730
+ if (similarMatches.length > 1) {
4731
+ return {
4732
+ found: false,
4733
+ matches: similarMatches.map((r) => r.item)
4734
+ };
4735
+ }
4736
+ if (bestScore <= FUZZY_THRESHOLD) {
4737
+ return {
4738
+ found: true,
4739
+ data: results[0].item
4740
+ };
4741
+ }
4742
+ return {
4743
+ found: false,
4744
+ matches: results.map((r) => r.item)
4745
+ };
4746
+ }
4747
+
4748
+ // src/commands/implement.ts
3211
4749
  var DEFAULT_MAX_CYCLES = 10;
3212
4750
  var DEFAULT_MAX_TIME = 60;
3213
4751
  var DEFAULT_MODEL = "opus";
@@ -3231,43 +4769,23 @@ var WORKER_OUTPUT_SCHEMA = {
3231
4769
  },
3232
4770
  required: ["status", "summary"]
3233
4771
  };
3234
- function findStory(projectPath, storySlug) {
3235
- const worktreesDir = (0, import_node_path3.join)(projectPath, ".saga", "worktrees");
3236
- if (!(0, import_node_fs3.existsSync)(worktreesDir)) {
4772
+ function findStory2(projectPath, storySlug) {
4773
+ const result = findStory(projectPath, storySlug);
4774
+ if (!result.found) {
3237
4775
  return null;
3238
4776
  }
3239
- const epicDirs = (0, import_node_fs3.readdirSync)(worktreesDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
3240
- for (const epicSlug of epicDirs) {
3241
- const epicWorktreeDir = (0, import_node_path3.join)(worktreesDir, epicSlug);
3242
- const storyWorktree = (0, import_node_path3.join)(epicWorktreeDir, storySlug);
3243
- if (!(0, import_node_fs3.existsSync)(storyWorktree)) {
3244
- continue;
3245
- }
3246
- const storyPath = (0, import_node_path3.join)(
3247
- storyWorktree,
3248
- ".saga",
3249
- "epics",
3250
- epicSlug,
3251
- "stories",
3252
- storySlug,
3253
- "story.md"
3254
- );
3255
- if ((0, import_node_fs3.existsSync)(storyPath)) {
3256
- return {
3257
- epicSlug,
3258
- storySlug,
3259
- storyPath,
3260
- worktreePath: storyWorktree
3261
- };
3262
- }
3263
- }
3264
- return null;
4777
+ return {
4778
+ epicSlug: result.data.epicSlug,
4779
+ storySlug: result.data.slug,
4780
+ storyPath: result.data.storyPath,
4781
+ worktreePath: result.data.worktreePath
4782
+ };
3265
4783
  }
3266
4784
  function computeStoryPath(worktree, epicSlug, storySlug) {
3267
- return (0, import_node_path3.join)(worktree, ".saga", "epics", epicSlug, "stories", storySlug, "story.md");
4785
+ return (0, import_node_path4.join)(worktree, ".saga", "epics", epicSlug, "stories", storySlug, "story.md");
3268
4786
  }
3269
4787
  function validateStoryFiles(worktree, epicSlug, storySlug) {
3270
- if (!(0, import_node_fs3.existsSync)(worktree)) {
4788
+ if (!(0, import_node_fs4.existsSync)(worktree)) {
3271
4789
  return {
3272
4790
  valid: false,
3273
4791
  error: `Worktree not found at ${worktree}
@@ -3280,7 +4798,7 @@ To create the worktree, use: /task-resume ${storySlug}`
3280
4798
  };
3281
4799
  }
3282
4800
  const storyPath = computeStoryPath(worktree, epicSlug, storySlug);
3283
- if (!(0, import_node_fs3.existsSync)(storyPath)) {
4801
+ if (!(0, import_node_fs4.existsSync)(storyPath)) {
3284
4802
  return {
3285
4803
  valid: false,
3286
4804
  error: `story.md not found in worktree.
@@ -3294,7 +4812,7 @@ This may indicate an incomplete story setup.`
3294
4812
  return { valid: true };
3295
4813
  }
3296
4814
  function getSkillRoot(pluginRoot) {
3297
- return (0, import_node_path3.join)(pluginRoot, "skills", "execute-story");
4815
+ return (0, import_node_path4.join)(pluginRoot, "skills", "execute-story");
3298
4816
  }
3299
4817
  function checkCommandExists(command) {
3300
4818
  try {
@@ -3334,8 +4852,8 @@ function runDryRun(storyInfo, projectPath, pluginRoot) {
3334
4852
  if (!claudeCheck.exists) allPassed = false;
3335
4853
  if (pluginRoot) {
3336
4854
  const skillRoot = getSkillRoot(pluginRoot);
3337
- const workerPromptPath = (0, import_node_path3.join)(skillRoot, WORKER_PROMPT_RELATIVE);
3338
- if ((0, import_node_fs3.existsSync)(workerPromptPath)) {
4855
+ const workerPromptPath = (0, import_node_path4.join)(skillRoot, WORKER_PROMPT_RELATIVE);
4856
+ if ((0, import_node_fs4.existsSync)(workerPromptPath)) {
3339
4857
  checks.push({
3340
4858
  name: "Worker prompt",
3341
4859
  path: workerPromptPath,
@@ -3356,7 +4874,7 @@ function runDryRun(storyInfo, projectPath, pluginRoot) {
3356
4874
  path: `${storyInfo.storySlug} (epic: ${storyInfo.epicSlug})`,
3357
4875
  passed: true
3358
4876
  });
3359
- if ((0, import_node_fs3.existsSync)(storyInfo.worktreePath)) {
4877
+ if ((0, import_node_fs4.existsSync)(storyInfo.worktreePath)) {
3360
4878
  checks.push({
3361
4879
  name: "Worktree exists",
3362
4880
  path: storyInfo.worktreePath,
@@ -3371,13 +4889,13 @@ function runDryRun(storyInfo, projectPath, pluginRoot) {
3371
4889
  });
3372
4890
  allPassed = false;
3373
4891
  }
3374
- if ((0, import_node_fs3.existsSync)(storyInfo.worktreePath)) {
4892
+ if ((0, import_node_fs4.existsSync)(storyInfo.worktreePath)) {
3375
4893
  const storyMdPath = computeStoryPath(
3376
4894
  storyInfo.worktreePath,
3377
4895
  storyInfo.epicSlug,
3378
4896
  storyInfo.storySlug
3379
4897
  );
3380
- if ((0, import_node_fs3.existsSync)(storyMdPath)) {
4898
+ if ((0, import_node_fs4.existsSync)(storyMdPath)) {
3381
4899
  checks.push({
3382
4900
  name: "story.md in worktree",
3383
4901
  path: storyMdPath,
@@ -3427,11 +4945,11 @@ function printDryRunResults(result) {
3427
4945
  }
3428
4946
  function loadWorkerPrompt(pluginRoot) {
3429
4947
  const skillRoot = getSkillRoot(pluginRoot);
3430
- const promptPath = (0, import_node_path3.join)(skillRoot, WORKER_PROMPT_RELATIVE);
3431
- if (!(0, import_node_fs3.existsSync)(promptPath)) {
4948
+ const promptPath = (0, import_node_path4.join)(skillRoot, WORKER_PROMPT_RELATIVE);
4949
+ if (!(0, import_node_fs4.existsSync)(promptPath)) {
3432
4950
  throw new Error(`Worker prompt not found at ${promptPath}`);
3433
4951
  }
3434
- return (0, import_node_fs3.readFileSync)(promptPath, "utf-8");
4952
+ return (0, import_node_fs4.readFileSync)(promptPath, "utf-8");
3435
4953
  }
3436
4954
  function buildScopeSettings() {
3437
4955
  const hookCommand = "npx @saga-ai/cli scope-validator";
@@ -3505,7 +5023,7 @@ function spawnWorker(prompt, model, settings, workingDir) {
3505
5023
  return result.stdout || "";
3506
5024
  }
3507
5025
  function runLoop(epicSlug, storySlug, maxCycles, maxTime, model, projectDir, pluginRoot) {
3508
- const worktree = (0, import_node_path3.join)(projectDir, ".saga", "worktrees", epicSlug, storySlug);
5026
+ const worktree = (0, import_node_path4.join)(projectDir, ".saga", "worktrees", epicSlug, storySlug);
3509
5027
  const validation = validateStoryFiles(worktree, epicSlug, storySlug);
3510
5028
  if (!validation.valid) {
3511
5029
  return {
@@ -3607,11 +5125,11 @@ async function implementCommand(storySlug, options) {
3607
5125
  console.error(`Error: ${error.message}`);
3608
5126
  process.exit(1);
3609
5127
  }
3610
- const storyInfo = findStory(projectPath, storySlug);
5128
+ const storyInfo = findStory2(projectPath, storySlug);
3611
5129
  if (!storyInfo) {
3612
5130
  console.error(`Error: Story '${storySlug}' not found in SAGA project.`);
3613
5131
  console.error(`
3614
- Searched in: ${(0, import_node_path3.join)(projectPath, ".saga", "worktrees")}`);
5132
+ Searched in: ${(0, import_node_path4.join)(projectPath, ".saga", "worktrees")}`);
3615
5133
  console.error("\nMake sure the story worktree exists and has a story.md file.");
3616
5134
  console.error("Run /generate-stories to create story worktrees.");
3617
5135
  process.exit(1);
@@ -3622,7 +5140,7 @@ Searched in: ${(0, import_node_path3.join)(projectPath, ".saga", "worktrees")}`)
3622
5140
  printDryRunResults(dryRunResult);
3623
5141
  process.exit(dryRunResult.success ? 0 : 1);
3624
5142
  }
3625
- if (!(0, import_node_fs3.existsSync)(storyInfo.worktreePath)) {
5143
+ if (!(0, import_node_fs4.existsSync)(storyInfo.worktreePath)) {
3626
5144
  console.error(`Error: Worktree not found at ${storyInfo.worktreePath}`);
3627
5145
  console.error("\nThe story worktree has not been created yet.");
3628
5146
  console.error("Make sure the story was properly generated with /generate-stories.");
@@ -3767,9 +5285,31 @@ async function scopeValidatorCommand() {
3767
5285
  process.exit(0);
3768
5286
  }
3769
5287
 
5288
+ // src/commands/find.ts
5289
+ async function findCommand(query, options) {
5290
+ let projectPath;
5291
+ try {
5292
+ projectPath = resolveProjectPath(options.path);
5293
+ } catch (error) {
5294
+ console.log(JSON.stringify({ found: false, error: error.message }));
5295
+ process.exit(1);
5296
+ }
5297
+ const type = options.type ?? "story";
5298
+ let result;
5299
+ if (type === "epic") {
5300
+ result = findEpic(projectPath, query);
5301
+ } else {
5302
+ result = findStory(projectPath, query);
5303
+ }
5304
+ console.log(JSON.stringify(result, null, 2));
5305
+ if (!result.found) {
5306
+ process.exit(1);
5307
+ }
5308
+ }
5309
+
3770
5310
  // src/cli.ts
3771
- var packageJsonPath = (0, import_node_path4.join)(__dirname, "..", "package.json");
3772
- var packageJson = JSON.parse((0, import_node_fs4.readFileSync)(packageJsonPath, "utf-8"));
5311
+ var packageJsonPath = (0, import_node_path5.join)(__dirname, "..", "package.json");
5312
+ var packageJson = JSON.parse((0, import_node_fs5.readFileSync)(packageJsonPath, "utf-8"));
3773
5313
  var program2 = new Command();
3774
5314
  program2.name("saga").description("CLI for SAGA - Structured Autonomous Goal Achievement").version(packageJson.version).addHelpCommand("help [command]", "Display help for a command");
3775
5315
  program2.option("-p, --path <dir>", "Path to SAGA project directory (overrides auto-discovery)");
@@ -3787,6 +5327,13 @@ program2.command("implement <story-slug>").description("Run story implementation
3787
5327
  dryRun: options.dryRun
3788
5328
  });
3789
5329
  });
5330
+ program2.command("find <query>").description("Find an epic or story by slug/title").option("--type <type>", "Type to search for: epic or story (default: story)").action(async (query, options) => {
5331
+ const globalOpts = program2.opts();
5332
+ await findCommand(query, {
5333
+ path: globalOpts.path,
5334
+ type: options.type
5335
+ });
5336
+ });
3790
5337
  program2.command("dashboard").description("Start the dashboard server").option("--port <n>", "Port to run the server on (default: 3847)", parseInt).action(async (options) => {
3791
5338
  const globalOpts = program2.opts();
3792
5339
  await dashboardCommand({