{"version":3,"file":"strategy.d.ts","sourceRoot":"","sources":["../../../../src/core/long-horizon/adaptive/strategy.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,iBAAiB,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEhG,MAAM,WAAW,WAAW;IAC3B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,uBAAuB,EAAE,MAAM,CAAC;CAChC;AAED,MAAM,WAAW,YAAY;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,eAAe,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC;IAC1C,eAAe,EAAE,SAAS,iBAAiB,EAAE,CAAC;CAC9C;AAED,MAAM,WAAW,cAAc;IAC9B,OAAO,EAAE,YAAY,CAAC;IACtB,MAAM,EAAE,WAAW,CAAC;CACpB;AAED,iFAAiF;AACjF,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,iBAAiB,EAAE,cAAc,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAe3G;AAMD,mGAAmG;AACnG,wBAAgB,YAAY,CAAC,GAAG,EAAE,YAAY,EAAE,SAAS,EAAE,SAAS,iBAAiB,EAAE,GAAG,OAAO,CAUhG;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,SAAS,EAAE,cAAc,EAAE,GAAG,EAAE,YAAY,GAAG,WAAW,CAyDvF;AAMD,8EAA8E;AAC9E,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,SAAS,iBAAiB,EAAE,EAAE,MAAM,EAAE,iBAAiB,GAAG,MAAM,CAMhH","sourcesContent":["/**\n * Strategy representation and bounded pivots.\n *\n * A pivot is allowed only when it is materially different, evidence-backed, does\n * not expand scope or relax safety, and stays inside the remaining pivot budget.\n * A cosmetic rewrite of the same commands is rejected. Once pivots are\n * exhausted with no safe materially different strategy, blocks with\n * STRATEGY_EXHAUSTED.\n */\n\nimport type { ExecutionBudget, ExecutionStrategy, PivotRequest, PivotResult } from \"./types.js\";\n\nexport interface PivotBudget {\n\tmaxStrategyPivots: number;\n\tmaxPivotsPerPhase: number;\n\tmaxEquivalentStrategies: number;\n}\n\nexport interface PivotContext {\n\tusedPivots: number;\n\tusedPivotsThisPhase: number;\n\tphase: string;\n\tremainingBudget: Partial<ExecutionBudget>;\n\tstrategyHistory: readonly ExecutionStrategy[];\n}\n\nexport interface PivotEvaluator {\n\tcontext: PivotContext;\n\tbudget: PivotBudget;\n}\n\n/** Compare the projected actions of a pivot to the failed strategy's actions. */\nexport function isMateriallyDifferent(failed: ExecutionStrategy, plannedActions: readonly string[]): boolean {\n\t// Reject a cosmetic rename of the same command sequence.\n\tconst failedActions = failed.plannedActions.map((a) => a.trim());\n\tconst planned = plannedActions.map((a) => a.trim());\n\tif (failedActions.length === planned.length) {\n\t\tlet same = true;\n\t\tfor (let i = 0; i < failedActions.length; i += 1) {\n\t\t\tif (normAction(failedActions[i]) !== normAction(planned[i])) {\n\t\t\t\tsame = false;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif (same) return false;\n\t}\n\treturn true;\n}\n\nfunction normAction(a: string): string {\n\treturn a.replace(/\\s+/gu, \" \").toLowerCase().trim();\n}\n\n/** A pivot is a true scope-reducer or different-strategy only if it changes the hazard profile. */\nexport function scopeExpands(req: PivotRequest, _existing: readonly ExecutionStrategy[]): boolean {\n\t// Any action that was not present before AND is a mutation/verification\n\t// broadening counts as scope expansion. For this deterministic model we flag\n\t// requests whose planned actions contain new mutating verbs not in history.\n\tconst scopeVerbs = new Set([\"delete\", \"drop\", \"rm\", \"reset\", \"force\", \"push\", \"publish\", \"merge\"]);\n\tfor (const action of req.materialChange) {\n\t\tconst first = normAction(action).split(/[ /]/u)[0];\n\t\tif (scopeVerbs.has(first)) return true;\n\t}\n\treturn false;\n}\n\n/**\n * Evaluate a pivot request deterministically. Returns an approved new strategy\n * or a structured rejection.\n */\nexport function evaluatePivot(evaluator: PivotEvaluator, req: PivotRequest): PivotResult {\n\tconst blockedCodes: string[] = [];\n\n\t// Find the failed strategy.\n\tconst failed = evaluator.context.strategyHistory.find((s) => s.strategyId === req.failedStrategyId);\n\tif (!failed) {\n\t\treturn { ok: false, reasonCodes: [\"FAILED_STRATEGY_NOT_FOUND\"], error: \"failed strategy missing\" };\n\t}\n\tif (failed.status !== \"failed\" && failed.status !== \"superseded\" && failed.status !== \"blocked\") {\n\t\treturn { ok: false, reasonCodes: [\"FAILED_STRATEGY_NOT_FAILED\"], error: \"strategy not failed\" };\n\t}\n\n\tif (evaluator.context.usedPivots >= evaluator.budget.maxStrategyPivots) {\n\t\tblockedCodes.push(\"STRATEGY_EXHAUSTED\");\n\t}\n\tif (evaluator.context.usedPivotsThisPhase >= evaluator.budget.maxPivotsPerPhase) {\n\t\tblockedCodes.push(\"PHASE_PIVOT_LIMIT\");\n\t}\n\n\t// Reject cosmetic pivots.\n\tif (!isMateriallyDifferent(failed, req.plannedActions)) {\n\t\tblockedCodes.push(\"COSMETIC_PIVOT_REJECTED\");\n\t}\n\n\t// Reject scope expansion.\n\tif (scopeExpands(req, evaluator.context.strategyHistory)) {\n\t\tblockedCodes.push(\"SCOPE_EXPANSION_REJECTED\");\n\t}\n\n\tif (blockedCodes.length > 0) {\n\t\tconst exhausted = blockedCodes.includes(\"STRATEGY_EXHAUSTED\");\n\t\treturn {\n\t\t\tok: false,\n\t\t\treasonCodes: blockedCodes,\n\t\t\tblocked: true,\n\t\t\terror: exhausted ? \"STRATEGY_EXHAUSTED\" : undefined,\n\t\t};\n\t}\n\n\tconst strategy: ExecutionStrategy = Object.freeze({\n\t\tstrategyId: `S${evaluator.context.strategyHistory.length + 1}`,\n\t\tobjectiveId: req.objectiveId,\n\t\thypothesis: req.evidenceBackedReason,\n\t\tplannedActions: Object.freeze([...req.plannedActions]),\n\t\texpectedProgressSignals: Object.freeze([...req.newExpectedProgressSignals]),\n\t\tvalidationCriteria: Object.freeze([...req.validationCriteria]),\n\t\testimatedBudget: Object.freeze({ ...req.newEstimatedBudget }),\n\t\triskClass: req.riskClass,\n\t\tstatus: \"active\",\n\t});\n\n\treturn {\n\t\tok: true,\n\t\tstrategy,\n\t\tbudgetRemaining: remainingDiscretionary(evaluator.context.remainingBudget),\n\t\treasonCodes: [\"PIVOT_APPROVED\", \"MATERIALLY_DIFFERENT\"],\n\t};\n}\n\nfunction remainingDiscretionary(budget: Partial<ExecutionBudget>): number {\n\treturn budget.maxStrategyPivots ?? 0;\n}\n\n/** Count how many active/proposed strategies share the same action vector. */\nexport function equivalentStrategyCount(history: readonly ExecutionStrategy[], target: ExecutionStrategy): number {\n\tlet count = 0;\n\tfor (const s of history) {\n\t\tif (sameActionSet(s.plannedActions, target.plannedActions)) count += 1;\n\t}\n\treturn count;\n}\n\nfunction sameActionSet(a: readonly string[], b: readonly string[]): boolean {\n\tif (a.length !== b.length) return false;\n\tconst setA = a.map(normAction).sort();\n\tconst setB = b.map(normAction).sort();\n\tfor (let i = 0; i < setA.length; i += 1) {\n\t\tif (setA[i] !== setB[i]) return false;\n\t}\n\treturn true;\n}\n"]}