@smeltjs/core 0.5.0 → 0.6.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 (97) hide show
  1. package/dist/apply.d.ts.map +1 -1
  2. package/dist/apply.js +8 -2
  3. package/dist/apply.js.map +1 -1
  4. package/dist/cli/init.d.ts.map +1 -1
  5. package/dist/cli/init.js +4 -2
  6. package/dist/cli/init.js.map +1 -1
  7. package/dist/cli/report.d.ts +10 -1
  8. package/dist/cli/report.d.ts.map +1 -1
  9. package/dist/cli/report.js +16 -1
  10. package/dist/cli/report.js.map +1 -1
  11. package/dist/cli/subcommands/flags.d.ts +3 -0
  12. package/dist/cli/subcommands/flags.d.ts.map +1 -1
  13. package/dist/cli/subcommands/flags.js +16 -3
  14. package/dist/cli/subcommands/flags.js.map +1 -1
  15. package/dist/cli/subcommands/smelt.d.ts +3 -0
  16. package/dist/cli/subcommands/smelt.d.ts.map +1 -1
  17. package/dist/cli/subcommands/smelt.js +5 -1
  18. package/dist/cli/subcommands/smelt.js.map +1 -1
  19. package/dist/cli/subcommands/stats.d.ts +8 -3
  20. package/dist/cli/subcommands/stats.d.ts.map +1 -1
  21. package/dist/cli/subcommands/stats.js +15 -5
  22. package/dist/cli/subcommands/stats.js.map +1 -1
  23. package/dist/errors.d.ts +10 -0
  24. package/dist/errors.d.ts.map +1 -1
  25. package/dist/errors.js +10 -0
  26. package/dist/errors.js.map +1 -1
  27. package/dist/hooks/focus-terms.d.ts +57 -0
  28. package/dist/hooks/focus-terms.d.ts.map +1 -0
  29. package/dist/hooks/focus-terms.js +230 -0
  30. package/dist/hooks/focus-terms.js.map +1 -0
  31. package/dist/hooks/guard-core.d.ts +8 -16
  32. package/dist/hooks/guard-core.d.ts.map +1 -1
  33. package/dist/hooks/guard-core.js +22 -120
  34. package/dist/hooks/guard-core.js.map +1 -1
  35. package/dist/index.d.ts +4 -4
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +3 -3
  38. package/dist/index.js.map +1 -1
  39. package/dist/ops/index.d.ts +3 -3
  40. package/dist/ops/index.d.ts.map +1 -1
  41. package/dist/ops/index.js +2 -2
  42. package/dist/ops/index.js.map +1 -1
  43. package/dist/ops/verbs.d.ts +57 -1
  44. package/dist/ops/verbs.d.ts.map +1 -1
  45. package/dist/ops/verbs.js +49 -1
  46. package/dist/ops/verbs.js.map +1 -1
  47. package/dist/plan/auto.d.ts +9 -2
  48. package/dist/plan/auto.d.ts.map +1 -1
  49. package/dist/plan/auto.js +14 -2
  50. package/dist/plan/auto.js.map +1 -1
  51. package/dist/plan/diff.d.ts +45 -0
  52. package/dist/plan/diff.d.ts.map +1 -0
  53. package/dist/plan/diff.js +284 -0
  54. package/dist/plan/diff.js.map +1 -0
  55. package/dist/plan/json.d.ts +37 -0
  56. package/dist/plan/json.d.ts.map +1 -0
  57. package/dist/plan/json.js +181 -0
  58. package/dist/plan/json.js.map +1 -0
  59. package/dist/plan/kind.d.ts +28 -0
  60. package/dist/plan/kind.d.ts.map +1 -0
  61. package/dist/plan/kind.js +57 -0
  62. package/dist/plan/kind.js.map +1 -0
  63. package/dist/plan/offsets.d.ts +8 -0
  64. package/dist/plan/offsets.d.ts.map +1 -0
  65. package/dist/plan/offsets.js +19 -0
  66. package/dist/plan/offsets.js.map +1 -0
  67. package/dist/plan/planners.d.ts +15 -5
  68. package/dist/plan/planners.d.ts.map +1 -1
  69. package/dist/plan/planners.js +13 -5
  70. package/dist/plan/planners.js.map +1 -1
  71. package/dist/plan/structural.d.ts.map +1 -1
  72. package/dist/plan/structural.js +106 -14
  73. package/dist/plan/structural.js.map +1 -1
  74. package/dist/retrieve.d.ts +22 -1
  75. package/dist/retrieve.d.ts.map +1 -1
  76. package/dist/retrieve.js +59 -0
  77. package/dist/retrieve.js.map +1 -1
  78. package/dist/smelter.d.ts +4 -0
  79. package/dist/smelter.d.ts.map +1 -1
  80. package/dist/smelter.js +4 -0
  81. package/dist/smelter.js.map +1 -1
  82. package/dist/stats.d.ts +16 -1
  83. package/dist/stats.d.ts.map +1 -1
  84. package/dist/stats.js +29 -0
  85. package/dist/stats.js.map +1 -1
  86. package/dist/store-dir.d.ts +9 -2
  87. package/dist/store-dir.d.ts.map +1 -1
  88. package/dist/store-dir.js +48 -7
  89. package/dist/store-dir.js.map +1 -1
  90. package/dist/store.d.ts +4 -2
  91. package/dist/store.d.ts.map +1 -1
  92. package/dist/store.js +12 -2
  93. package/dist/store.js.map +1 -1
  94. package/dist/types.d.ts +116 -2
  95. package/dist/types.d.ts.map +1 -1
  96. package/dist/types.js.map +1 -1
  97. package/package.json +1 -1
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ApplyOptions, MarkerBuilder, MarkerInfo } from './apply.ts';\n\n/**\n * The public surface: a barrel over the modules that hold the reasoning.\n *\n * `createSmelter()` itself lives in `./smelter.ts` so that nothing inside `src/` has\n * to import this file to build a smelter — the CLI's default verb does exactly that,\n * and a barrel that imports the CLI which imports the barrel is a cycle whose only\n * symptom is a registry evaluating to `undefined` in whichever module the loader\n * entered first. Every name is re-exported here, so consumers see no difference.\n */\n\nexport type { ApplyOptions, MarkerBuilder, MarkerInfo };\nexport {\n applyPlan,\n defaultMarker,\n MARKER_FORMAT_VERSION,\n markerPricing,\n reconstruct,\n} from './apply.ts';\nexport { detectLanguage, SUPPORTED_LANGUAGES } from './detect.ts';\nexport * from './errors.ts';\nexport { contentHash, HASH_LENGTH } from './hash.ts';\nexport {\n ALLOWED_NODE_BUILTINS,\n ALLOWED_PACKAGES,\n ALLOWED_URL_SCHEMES,\n assertLocalResource,\n FORBIDDEN_GLOBALS,\n FORBIDDEN_NODE_MODULES,\n FORBIDDEN_PACKAGES,\n} from './net/policy.ts';\nexport type { LocalResource } from './net/policy.ts';\nexport { clearGrammarCache, grammarPath, loadGrammar, WASM_BY_LANGUAGE } from './plan/grammar.ts';\nexport { AUTO_PLANNER_ID, AutoPlanner, planAuto } from './plan/auto.ts';\nexport type { AutoPlannerOptions } from './plan/auto.ts';\nexport { LEXICAL_PLANNER_ID, LexicalPlanner, planLexical } from './plan/lexical.ts';\nexport type { LexicalPlannerOptions } from './plan/lexical.ts';\nexport { DEFAULT_STRATEGY, isStrategy, PLANNERS, STRATEGIES } from './plan/planners.ts';\nexport type { PlannerFactoryOptions } from './plan/planners.ts';\nexport {\n isStructuralLanguage,\n planStructural,\n STRUCTURAL_LANGUAGES,\n STRUCTURAL_PLANNER_ID,\n StructuralPlanner,\n} from './plan/structural.ts';\nexport type { StructuralLanguage, StructuralPlannerOptions } from './plan/structural.ts';\nexport { createRetrieveTool, RETRIEVE_TOOL_NAME } from './retrieve.ts';\nexport { unconfiguredDistillStage, unconfiguredRerankStage } from './stages.ts';\nexport { MemoryElisionStore } from './store.ts';\nexport {\n DIRECTORY_STORE_FORMAT,\n DIRECTORY_STORE_VERSION,\n DirectoryElisionStore,\n} from './store-dir.ts';\nexport type { DirectoryElisionStoreOptions } from './store-dir.ts';\nexport * from './types.ts';\nexport {\n CLI_JSON_FORMAT,\n CLI_NAME,\n cliUsage,\n EXIT,\n formatReport,\n parseSmeltArgs,\n runCli,\n} from './cli/run.ts';\nexport type { AnswerStream, CliIo, CliJsonEnvelope, SmeltInvocation } from './cli/run.ts';\nexport {\n ANTHROPIC_PROMPT_CACHE_FACTS,\n CACHE_BREAKER_RULES,\n detectCacheBreakers,\n findPrefixDivergence,\n} from './cache/prefix.ts';\nexport type {\n CacheWarning,\n PrefixDivergence,\n PromptStructure,\n PromptTool,\n} from './cache/prefix.ts';\nexport { MARKER_LINE_COMMENT_LEADERS, markerForLanguage } from './apply.ts';\nexport {\n buildRepoMap,\n DEFAULT_REPO_IGNORE,\n REPO_MAP_CACHE_CORRUPT_RULE,\n REPO_MAP_ID,\n REPO_MAP_PATH_ONLY_RULE,\n REPO_MAP_RANKED_RULE,\n REPO_MAP_UNREFERENCED_RULE,\n} from './repomap/map.ts';\nexport type {\n RepoMap,\n RepoMapCacheCounts,\n RepoMapEntry,\n RepoMapOptions,\n RepoMapPathEntry,\n RepoMapReason,\n RepoMapWarning,\n} from './repomap/map.ts';\nexport { PAGERANK_DAMPING, PAGERANK_ITERATIONS, rankDefinitions } from './repomap/rank.ts';\nexport type { FileTagsEntry, RankedDefinition } from './repomap/rank.ts';\nexport { extractTags } from './repomap/tags.ts';\nexport type { DefinitionTag, FileTags, ReferenceTag } from './repomap/tags.ts';\nexport { TAGS_CACHE_FORMAT, TAGS_CACHE_VERSION, tagsCacheKey } from './repomap/cache.ts';\nexport { nodeFsReader } from './repomap/reader.ts';\nexport type { DirEntry, FileStat, RepoReader } from './repomap/reader.ts';\nexport {\n CONFIG_FILE_NAME,\n CONFIG_VERSION,\n configuredStore,\n findConfigFile,\n loadNearestConfig,\n parseConfig,\n renderConfig,\n resolveStorePath,\n} from './cli/config.ts';\nexport type { ConfiguredStore, LoadedConfig, SmeltConfig, SmeltConfigStore } from './cli/config.ts';\nexport {\n MEASURE_STUB_FILE,\n measureStubSource,\n RERANK_STUB_FILE,\n rerankStubSource,\n runInit,\n} from './cli/init.ts';\nexport type { InitIo } from './cli/init.ts';\nexport { runSetup } from './cli/setup.ts';\nexport type {\n SetupCheck,\n SetupFileAction,\n SetupIo,\n SetupOptions,\n SetupReceipt,\n} from './cli/setup.ts';\nexport { runDoctor } from './cli/doctor.ts';\nexport type {\n DoctorBlock,\n DoctorConfig,\n DoctorIo,\n DoctorMcp,\n DoctorOptions,\n DoctorReceipt,\n} from './cli/doctor.ts';\nexport { retrieveStats } from './stats.ts';\nexport type { RawRetrieveCounters } from './stats.ts';\n\n/**\n * The SetupRecipe: the one true way to put smelt on a machine, as data. Public for the\n * same reason the harness views are — something outside this package renders it (the\n * site's fact generator), and the docs and skill are pinned to it rather than retyping\n * it. See `src/setup/recipe.ts`.\n */\nexport { SETUP_RECIPE, SETUP_STEPS } from './setup/recipe.ts';\nexport type { SetupRecipe, SetupStep } from './setup/recipe.ts';\nexport {\n LANGUAGE_PROFILES,\n profileFor,\n profileForPath,\n structuralLanguages,\n} from './lang/registry.ts';\nexport type { LanguageProfile, LanguageStructure, RepoMapFacts } from './lang/profile.ts';\n/**\n * The harness registry's rendered views. Public for the same reason the ops seam is:\n * something outside this package renders them — the site's `facts.json` generator —\n * and the alternative is a second copy of the tier table typed into a React component,\n * which is exactly the drift `harnessesByTier()` exists to end.\n */\nexport { harnessesByTier, harnessNames, HARNESSES, HARNESS_IDS } from './harness/registry.ts';\nexport type { HarnessTierGroup } from './harness/registry.ts';\nexport { harnessLabel, HARNESS_TIERS, TIER_HONESTY } from './harness/profile.ts';\nexport type { HarnessId, HarnessTier } from './harness/profile.ts';\nexport { resolveRun } from './cli/subcommands/smelt.ts';\nexport type { ResolvedRun } from './cli/subcommands/smelt.ts';\nexport { REPO_MAP_FOCUS_RULE } from './repomap/map.ts';\nexport { CLI_MAP_JSON_FORMAT, formatMapReport, resolveMapRun } from './cli/run.ts';\nexport type { CliInvocation, CliMapJsonEnvelope, MapInvocation } from './cli/run.ts';\nexport type { MapReportInput } from './cli/report.ts';\nexport type { ResolvedMapRun } from './cli/subcommands/map.ts';\n\n/**\n * The operations seam — the four verbs and the laws their inputs must satisfy, below\n * every front door. `@smeltjs/mcp` consumes these as an ordinary dependency, so the\n * `smelt` CLI and the MCP tools run the same middle instead of two copies of it. See\n * `src/ops/index.ts` for what belongs here and what stays in an adapter.\n */\nexport {\n budgetFault,\n budgetMalformed,\n budgetRequired,\n mapTree,\n openStore,\n readBlob,\n readCounters,\n readTree,\n resolveStrategy,\n retrieveBytes,\n smeltBlob,\n} from './ops/index.ts';\nexport type {\n BudgetFault,\n BudgetNaming,\n MapTreeOp,\n ReadCountersOp,\n ResolvedStrategy,\n RetrieveBytesOp,\n Ruling,\n SmeltBlobOp,\n SmeltBlobOutcome,\n StrategySource,\n TreeNaming,\n} from './ops/index.ts';\n\n/**\n * Which planner a smelter uses, named by string. The names, their factories, and this\n * type all come from the one {@link PLANNERS} registry in `src/plan/planners.ts`, so\n * the CLI's validation and help text cannot drift from what `createSmelter` builds.\n */\nexport type { Strategy } from './plan/planners.ts';\n\nexport { createSmelter } from './smelter.ts';\nexport type { Smelter, SmelterConfig, SmeltCallOptions } from './smelter.ts';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ApplyOptions, MarkerBuilder, MarkerInfo } from './apply.ts';\n\n/**\n * The public surface: a barrel over the modules that hold the reasoning.\n *\n * `createSmelter()` itself lives in `./smelter.ts` so that nothing inside `src/` has\n * to import this file to build a smelter — the CLI's default verb does exactly that,\n * and a barrel that imports the CLI which imports the barrel is a cycle whose only\n * symptom is a registry evaluating to `undefined` in whichever module the loader\n * entered first. Every name is re-exported here, so consumers see no difference.\n */\n\nexport type { ApplyOptions, MarkerBuilder, MarkerInfo };\nexport {\n applyPlan,\n defaultMarker,\n MARKER_FORMAT_VERSION,\n markerPricing,\n reconstruct,\n} from './apply.ts';\nexport { detectLanguage, SUPPORTED_LANGUAGES } from './detect.ts';\nexport * from './errors.ts';\nexport { contentHash, HASH_LENGTH } from './hash.ts';\nexport {\n ALLOWED_NODE_BUILTINS,\n ALLOWED_PACKAGES,\n ALLOWED_URL_SCHEMES,\n assertLocalResource,\n FORBIDDEN_GLOBALS,\n FORBIDDEN_NODE_MODULES,\n FORBIDDEN_PACKAGES,\n} from './net/policy.ts';\nexport type { LocalResource } from './net/policy.ts';\nexport { clearGrammarCache, grammarPath, loadGrammar, WASM_BY_LANGUAGE } from './plan/grammar.ts';\nexport { AUTO_PLANNER_ID, AutoPlanner, planAuto } from './plan/auto.ts';\nexport type { AutoPlannerOptions } from './plan/auto.ts';\nexport { LEXICAL_PLANNER_ID, LexicalPlanner, planLexical } from './plan/lexical.ts';\nexport type { LexicalPlannerOptions } from './plan/lexical.ts';\nexport { DEFAULT_STRATEGY, isStrategy, PLANNERS, STRATEGIES } from './plan/planners.ts';\nexport type { PlannerFactoryOptions } from './plan/planners.ts';\nexport {\n isStructuralLanguage,\n planStructural,\n STRUCTURAL_LANGUAGES,\n STRUCTURAL_PLANNER_ID,\n StructuralPlanner,\n} from './plan/structural.ts';\nexport type { StructuralLanguage, StructuralPlannerOptions } from './plan/structural.ts';\nexport {\n createRetrieveBatchTool,\n createRetrieveTool,\n RETRIEVE_BATCH_TOOL_NAME,\n RETRIEVE_TOOL_NAME,\n} from './retrieve.ts';\nexport { unconfiguredDistillStage, unconfiguredRerankStage } from './stages.ts';\nexport { MemoryElisionStore } from './store.ts';\nexport {\n DIRECTORY_STORE_FORMAT,\n DIRECTORY_STORE_VERSION,\n DirectoryElisionStore,\n} from './store-dir.ts';\nexport type { DirectoryElisionStoreOptions } from './store-dir.ts';\nexport * from './types.ts';\nexport {\n CLI_JSON_FORMAT,\n CLI_NAME,\n cliUsage,\n EXIT,\n formatReport,\n parseSmeltArgs,\n runCli,\n} from './cli/run.ts';\nexport type { AnswerStream, CliIo, CliJsonEnvelope, SmeltInvocation } from './cli/run.ts';\nexport {\n ANTHROPIC_PROMPT_CACHE_FACTS,\n CACHE_BREAKER_RULES,\n detectCacheBreakers,\n findPrefixDivergence,\n} from './cache/prefix.ts';\nexport type {\n CacheWarning,\n PrefixDivergence,\n PromptStructure,\n PromptTool,\n} from './cache/prefix.ts';\nexport { MARKER_LINE_COMMENT_LEADERS, markerForLanguage } from './apply.ts';\nexport {\n buildRepoMap,\n DEFAULT_REPO_IGNORE,\n REPO_MAP_CACHE_CORRUPT_RULE,\n REPO_MAP_ID,\n REPO_MAP_PATH_ONLY_RULE,\n REPO_MAP_RANKED_RULE,\n REPO_MAP_UNREFERENCED_RULE,\n} from './repomap/map.ts';\nexport type {\n RepoMap,\n RepoMapCacheCounts,\n RepoMapEntry,\n RepoMapOptions,\n RepoMapPathEntry,\n RepoMapReason,\n RepoMapWarning,\n} from './repomap/map.ts';\nexport { PAGERANK_DAMPING, PAGERANK_ITERATIONS, rankDefinitions } from './repomap/rank.ts';\nexport type { FileTagsEntry, RankedDefinition } from './repomap/rank.ts';\nexport { extractTags } from './repomap/tags.ts';\nexport type { DefinitionTag, FileTags, ReferenceTag } from './repomap/tags.ts';\nexport { TAGS_CACHE_FORMAT, TAGS_CACHE_VERSION, tagsCacheKey } from './repomap/cache.ts';\nexport { nodeFsReader } from './repomap/reader.ts';\nexport type { DirEntry, FileStat, RepoReader } from './repomap/reader.ts';\nexport {\n CONFIG_FILE_NAME,\n CONFIG_VERSION,\n configuredStore,\n findConfigFile,\n loadNearestConfig,\n parseConfig,\n renderConfig,\n resolveStorePath,\n} from './cli/config.ts';\nexport type { ConfiguredStore, LoadedConfig, SmeltConfig, SmeltConfigStore } from './cli/config.ts';\nexport {\n MEASURE_STUB_FILE,\n measureStubSource,\n RERANK_STUB_FILE,\n rerankStubSource,\n runInit,\n} from './cli/init.ts';\nexport type { InitIo } from './cli/init.ts';\nexport { runSetup } from './cli/setup.ts';\nexport type {\n SetupCheck,\n SetupFileAction,\n SetupIo,\n SetupOptions,\n SetupReceipt,\n} from './cli/setup.ts';\nexport { runDoctor } from './cli/doctor.ts';\nexport type {\n DoctorBlock,\n DoctorConfig,\n DoctorIo,\n DoctorMcp,\n DoctorOptions,\n DoctorReceipt,\n} from './cli/doctor.ts';\nexport { retrieveStats, ruleLedger } from './stats.ts';\nexport type { RawRetrieveCounters } from './stats.ts';\n\n/**\n * The SetupRecipe: the one true way to put smelt on a machine, as data. Public for the\n * same reason the harness views are — something outside this package renders it (the\n * site's fact generator), and the docs and skill are pinned to it rather than retyping\n * it. See `src/setup/recipe.ts`.\n */\nexport { SETUP_RECIPE, SETUP_STEPS } from './setup/recipe.ts';\nexport type { SetupRecipe, SetupStep } from './setup/recipe.ts';\nexport {\n LANGUAGE_PROFILES,\n profileFor,\n profileForPath,\n structuralLanguages,\n} from './lang/registry.ts';\nexport type { LanguageProfile, LanguageStructure, RepoMapFacts } from './lang/profile.ts';\n/**\n * The harness registry's rendered views. Public for the same reason the ops seam is:\n * something outside this package renders them — the site's `facts.json` generator —\n * and the alternative is a second copy of the tier table typed into a React component,\n * which is exactly the drift `harnessesByTier()` exists to end.\n */\nexport { harnessesByTier, harnessNames, HARNESSES, HARNESS_IDS } from './harness/registry.ts';\nexport type { HarnessTierGroup } from './harness/registry.ts';\nexport { harnessLabel, HARNESS_TIERS, TIER_HONESTY } from './harness/profile.ts';\nexport type { HarnessId, HarnessTier } from './harness/profile.ts';\nexport { resolveRun } from './cli/subcommands/smelt.ts';\nexport type { ResolvedRun } from './cli/subcommands/smelt.ts';\nexport { REPO_MAP_FOCUS_RULE } from './repomap/map.ts';\nexport { CLI_MAP_JSON_FORMAT, formatMapReport, resolveMapRun } from './cli/run.ts';\nexport type { CliInvocation, CliMapJsonEnvelope, MapInvocation } from './cli/run.ts';\nexport type { MapReportInput } from './cli/report.ts';\nexport type { ResolvedMapRun } from './cli/subcommands/map.ts';\n\n/**\n * The operations seam — the four verbs and the laws their inputs must satisfy, below\n * every front door. `@smeltjs/mcp` consumes these as an ordinary dependency, so the\n * `smelt` CLI and the MCP tools run the same middle instead of two copies of it. See\n * `src/ops/index.ts` for what belongs here and what stays in an adapter.\n */\nexport {\n budgetFault,\n budgetMalformed,\n budgetRequired,\n mapTree,\n openStore,\n readBlob,\n readCounters,\n readLedger,\n readTree,\n resolveStrategy,\n retrieveBytes,\n retrieveMany,\n smeltBlob,\n} from './ops/index.ts';\nexport type {\n BudgetFault,\n BudgetNaming,\n FocusSource,\n MapTreeOp,\n ReadCountersOp,\n ReadLedgerOp,\n ResolvedFocus,\n ResolvedStrategy,\n RetrieveBytesOp,\n RetrieveManyOp,\n Ruling,\n SmeltBlobOp,\n SmeltBlobOutcome,\n StrategySource,\n TreeNaming,\n} from './ops/index.ts';\n\n/**\n * Which planner a smelter uses, named by string. The names, their factories, and this\n * type all come from the one {@link PLANNERS} registry in `src/plan/planners.ts`, so\n * the CLI's validation and help text cannot drift from what `createSmelter` builds.\n */\nexport type { Strategy } from './plan/planners.ts';\n\nexport { createSmelter } from './smelter.ts';\nexport type { Smelter, SmelterConfig, SmeltCallOptions } from './smelter.ts';\n"]}
@@ -20,7 +20,7 @@
20
20
  * So this module is the seam, below both doors:
21
21
  *
22
22
  * - **`ops/verbs.ts`** — {@link smeltBlob}, {@link mapTree}, {@link retrieveBytes},
23
- * {@link readCounters}. The verbs over already-resolved inputs, returning data.
23
+ * {@link retrieveMany}, {@link readCounters}. The verbs over already-resolved inputs, returning data.
24
24
  * - **`ops/inputs.ts`** — the laws an input must satisfy to *be* resolved, each
25
25
  * stating its rule once and taking the caller's naming as an argument.
26
26
  *
@@ -34,6 +34,6 @@
34
34
  */
35
35
  export { budgetFault, budgetMalformed, budgetRequired, openStore, readBlob, readTree, resolveStrategy, } from './inputs.ts';
36
36
  export type { BudgetFault, BudgetNaming, ResolvedStrategy, Ruling, StrategySource, TreeNaming, } from './inputs.ts';
37
- export { mapTree, readCounters, retrieveBytes, smeltBlob } from './verbs.ts';
38
- export type { MapTreeOp, ReadCountersOp, RetrieveBytesOp, SmeltBlobOp, SmeltBlobOutcome, } from './verbs.ts';
37
+ export { mapTree, readCounters, readLedger, retrieveBytes, retrieveMany, smeltBlob, } from './verbs.ts';
38
+ export type { FocusSource, MapTreeOp, ReadCountersOp, ReadLedgerOp, ResolvedFocus, RetrieveBytesOp, RetrieveManyOp, SmeltBlobOp, SmeltBlobOutcome, } from './verbs.ts';
39
39
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ops/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,EACL,WAAW,EACX,eAAe,EACf,cAAc,EACd,SAAS,EACT,QAAQ,EACR,QAAQ,EACR,eAAe,GAChB,MAAM,aAAa,CAAC;AACrB,YAAY,EACV,WAAW,EACX,YAAY,EACZ,gBAAgB,EAChB,MAAM,EACN,cAAc,EACd,UAAU,GACX,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC7E,YAAY,EACV,SAAS,EACT,cAAc,EACd,eAAe,EACf,WAAW,EACX,gBAAgB,GACjB,MAAM,YAAY,CAAC","sourcesContent":["/**\n * The operations seam: what `smelt` *does*, under every door that asks it to.\n *\n * smelt has two front doors — the `smelt` binary in this package and the\n * `@smeltjs/mcp` server next to it — and they are not two products. They are two\n * conventions for saying the same four things: cut this blob, map this tree, give\n * those bytes back, read the counters. The difference between them is entirely at the\n * edges: one reads argv and writes to two streams and returns an exit code, the other\n * validates a JSON Schema and returns a `CallToolResult`.\n *\n * The middle was duplicated anyway. Both packages had their own copy of *the budget is\n * a positive integer with no default*, *an explicit strategy beats a configured one\n * and `lexical` fills last*, *a tree reader refuses a file*, *read a path or fail\n * naming it*, and *a config decides a store*. Five laws, ten implementations — and the\n * mechanical cause is visible in one line of history: `resolveStoreRun` was never\n * exported, so the package that needed the store law could not import it and wrote its\n * own. A restatement leaked across a package boundary because the seam it needed sat\n * on the wrong side of a barrel.\n *\n * So this module is the seam, below both doors:\n *\n * - **`ops/verbs.ts`** — {@link smeltBlob}, {@link mapTree}, {@link retrieveBytes},\n * {@link readCounters}. The verbs over already-resolved inputs, returning data.\n * - **`ops/inputs.ts`** — the laws an input must satisfy to *be* resolved, each\n * stating its rule once and taking the caller's naming as an argument.\n *\n * A front door is now an adapter: **parse and resolve, call an op, render.** The CLI\n * subcommand bodies do that with flags, a config file and an exit code; the MCP tools\n * do it with a JSON Schema and a result envelope. What each may still keep is what is\n * genuinely its own — its error type, its exit code, its wording, and any policy it\n * deliberately does not share. (`smelt retrieve` refuses a memory store; the MCP\n * server accepts one and says so when it bites. That divergence is intentional, it is\n * documented on both sides, and it lives in the adapters — never here.)\n */\n\nexport {\n budgetFault,\n budgetMalformed,\n budgetRequired,\n openStore,\n readBlob,\n readTree,\n resolveStrategy,\n} from './inputs.ts';\nexport type {\n BudgetFault,\n BudgetNaming,\n ResolvedStrategy,\n Ruling,\n StrategySource,\n TreeNaming,\n} from './inputs.ts';\nexport { mapTree, readCounters, retrieveBytes, smeltBlob } from './verbs.ts';\nexport type {\n MapTreeOp,\n ReadCountersOp,\n RetrieveBytesOp,\n SmeltBlobOp,\n SmeltBlobOutcome,\n} from './verbs.ts';\n"]}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ops/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,EACL,WAAW,EACX,eAAe,EACf,cAAc,EACd,SAAS,EACT,QAAQ,EACR,QAAQ,EACR,eAAe,GAChB,MAAM,aAAa,CAAC;AACrB,YAAY,EACV,WAAW,EACX,YAAY,EACZ,gBAAgB,EAChB,MAAM,EACN,cAAc,EACd,UAAU,GACX,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,OAAO,EACP,YAAY,EACZ,UAAU,EACV,aAAa,EACb,YAAY,EACZ,SAAS,GACV,MAAM,YAAY,CAAC;AACpB,YAAY,EACV,WAAW,EACX,SAAS,EACT,cAAc,EACd,YAAY,EACZ,aAAa,EACb,eAAe,EACf,cAAc,EACd,WAAW,EACX,gBAAgB,GACjB,MAAM,YAAY,CAAC","sourcesContent":["/**\n * The operations seam: what `smelt` *does*, under every door that asks it to.\n *\n * smelt has two front doors — the `smelt` binary in this package and the\n * `@smeltjs/mcp` server next to it — and they are not two products. They are two\n * conventions for saying the same four things: cut this blob, map this tree, give\n * those bytes back, read the counters. The difference between them is entirely at the\n * edges: one reads argv and writes to two streams and returns an exit code, the other\n * validates a JSON Schema and returns a `CallToolResult`.\n *\n * The middle was duplicated anyway. Both packages had their own copy of *the budget is\n * a positive integer with no default*, *an explicit strategy beats a configured one\n * and `lexical` fills last*, *a tree reader refuses a file*, *read a path or fail\n * naming it*, and *a config decides a store*. Five laws, ten implementations — and the\n * mechanical cause is visible in one line of history: `resolveStoreRun` was never\n * exported, so the package that needed the store law could not import it and wrote its\n * own. A restatement leaked across a package boundary because the seam it needed sat\n * on the wrong side of a barrel.\n *\n * So this module is the seam, below both doors:\n *\n * - **`ops/verbs.ts`** — {@link smeltBlob}, {@link mapTree}, {@link retrieveBytes},\n * {@link retrieveMany}, {@link readCounters}. The verbs over already-resolved inputs, returning data.\n * - **`ops/inputs.ts`** — the laws an input must satisfy to *be* resolved, each\n * stating its rule once and taking the caller's naming as an argument.\n *\n * A front door is now an adapter: **parse and resolve, call an op, render.** The CLI\n * subcommand bodies do that with flags, a config file and an exit code; the MCP tools\n * do it with a JSON Schema and a result envelope. What each may still keep is what is\n * genuinely its own — its error type, its exit code, its wording, and any policy it\n * deliberately does not share. (`smelt retrieve` refuses a memory store; the MCP\n * server accepts one and says so when it bites. That divergence is intentional, it is\n * documented on both sides, and it lives in the adapters — never here.)\n */\n\nexport {\n budgetFault,\n budgetMalformed,\n budgetRequired,\n openStore,\n readBlob,\n readTree,\n resolveStrategy,\n} from './inputs.ts';\nexport type {\n BudgetFault,\n BudgetNaming,\n ResolvedStrategy,\n Ruling,\n StrategySource,\n TreeNaming,\n} from './inputs.ts';\nexport {\n mapTree,\n readCounters,\n readLedger,\n retrieveBytes,\n retrieveMany,\n smeltBlob,\n} from './verbs.ts';\nexport type {\n FocusSource,\n MapTreeOp,\n ReadCountersOp,\n ReadLedgerOp,\n ResolvedFocus,\n RetrieveBytesOp,\n RetrieveManyOp,\n SmeltBlobOp,\n SmeltBlobOutcome,\n} from './verbs.ts';\n"]}
package/dist/ops/index.js CHANGED
@@ -20,7 +20,7 @@
20
20
  * So this module is the seam, below both doors:
21
21
  *
22
22
  * - **`ops/verbs.ts`** — {@link smeltBlob}, {@link mapTree}, {@link retrieveBytes},
23
- * {@link readCounters}. The verbs over already-resolved inputs, returning data.
23
+ * {@link retrieveMany}, {@link readCounters}. The verbs over already-resolved inputs, returning data.
24
24
  * - **`ops/inputs.ts`** — the laws an input must satisfy to *be* resolved, each
25
25
  * stating its rule once and taking the caller's naming as an argument.
26
26
  *
@@ -33,5 +33,5 @@
33
33
  * documented on both sides, and it lives in the adapters — never here.)
34
34
  */
35
35
  export { budgetFault, budgetMalformed, budgetRequired, openStore, readBlob, readTree, resolveStrategy, } from './inputs.js';
36
- export { mapTree, readCounters, retrieveBytes, smeltBlob } from './verbs.js';
36
+ export { mapTree, readCounters, readLedger, retrieveBytes, retrieveMany, smeltBlob, } from './verbs.js';
37
37
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/ops/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,EACL,WAAW,EACX,eAAe,EACf,cAAc,EACd,SAAS,EACT,QAAQ,EACR,QAAQ,EACR,eAAe,GAChB,MAAM,aAAa,CAAC;AASrB,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC","sourcesContent":["/**\n * The operations seam: what `smelt` *does*, under every door that asks it to.\n *\n * smelt has two front doors — the `smelt` binary in this package and the\n * `@smeltjs/mcp` server next to it — and they are not two products. They are two\n * conventions for saying the same four things: cut this blob, map this tree, give\n * those bytes back, read the counters. The difference between them is entirely at the\n * edges: one reads argv and writes to two streams and returns an exit code, the other\n * validates a JSON Schema and returns a `CallToolResult`.\n *\n * The middle was duplicated anyway. Both packages had their own copy of *the budget is\n * a positive integer with no default*, *an explicit strategy beats a configured one\n * and `lexical` fills last*, *a tree reader refuses a file*, *read a path or fail\n * naming it*, and *a config decides a store*. Five laws, ten implementations — and the\n * mechanical cause is visible in one line of history: `resolveStoreRun` was never\n * exported, so the package that needed the store law could not import it and wrote its\n * own. A restatement leaked across a package boundary because the seam it needed sat\n * on the wrong side of a barrel.\n *\n * So this module is the seam, below both doors:\n *\n * - **`ops/verbs.ts`** — {@link smeltBlob}, {@link mapTree}, {@link retrieveBytes},\n * {@link readCounters}. The verbs over already-resolved inputs, returning data.\n * - **`ops/inputs.ts`** — the laws an input must satisfy to *be* resolved, each\n * stating its rule once and taking the caller's naming as an argument.\n *\n * A front door is now an adapter: **parse and resolve, call an op, render.** The CLI\n * subcommand bodies do that with flags, a config file and an exit code; the MCP tools\n * do it with a JSON Schema and a result envelope. What each may still keep is what is\n * genuinely its own — its error type, its exit code, its wording, and any policy it\n * deliberately does not share. (`smelt retrieve` refuses a memory store; the MCP\n * server accepts one and says so when it bites. That divergence is intentional, it is\n * documented on both sides, and it lives in the adapters — never here.)\n */\n\nexport {\n budgetFault,\n budgetMalformed,\n budgetRequired,\n openStore,\n readBlob,\n readTree,\n resolveStrategy,\n} from './inputs.ts';\nexport type {\n BudgetFault,\n BudgetNaming,\n ResolvedStrategy,\n Ruling,\n StrategySource,\n TreeNaming,\n} from './inputs.ts';\nexport { mapTree, readCounters, retrieveBytes, smeltBlob } from './verbs.ts';\nexport type {\n MapTreeOp,\n ReadCountersOp,\n RetrieveBytesOp,\n SmeltBlobOp,\n SmeltBlobOutcome,\n} from './verbs.ts';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/ops/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,EACL,WAAW,EACX,eAAe,EACf,cAAc,EACd,SAAS,EACT,QAAQ,EACR,QAAQ,EACR,eAAe,GAChB,MAAM,aAAa,CAAC;AASrB,OAAO,EACL,OAAO,EACP,YAAY,EACZ,UAAU,EACV,aAAa,EACb,YAAY,EACZ,SAAS,GACV,MAAM,YAAY,CAAC","sourcesContent":["/**\n * The operations seam: what `smelt` *does*, under every door that asks it to.\n *\n * smelt has two front doors — the `smelt` binary in this package and the\n * `@smeltjs/mcp` server next to it — and they are not two products. They are two\n * conventions for saying the same four things: cut this blob, map this tree, give\n * those bytes back, read the counters. The difference between them is entirely at the\n * edges: one reads argv and writes to two streams and returns an exit code, the other\n * validates a JSON Schema and returns a `CallToolResult`.\n *\n * The middle was duplicated anyway. Both packages had their own copy of *the budget is\n * a positive integer with no default*, *an explicit strategy beats a configured one\n * and `lexical` fills last*, *a tree reader refuses a file*, *read a path or fail\n * naming it*, and *a config decides a store*. Five laws, ten implementations — and the\n * mechanical cause is visible in one line of history: `resolveStoreRun` was never\n * exported, so the package that needed the store law could not import it and wrote its\n * own. A restatement leaked across a package boundary because the seam it needed sat\n * on the wrong side of a barrel.\n *\n * So this module is the seam, below both doors:\n *\n * - **`ops/verbs.ts`** — {@link smeltBlob}, {@link mapTree}, {@link retrieveBytes},\n * {@link retrieveMany}, {@link readCounters}. The verbs over already-resolved inputs, returning data.\n * - **`ops/inputs.ts`** — the laws an input must satisfy to *be* resolved, each\n * stating its rule once and taking the caller's naming as an argument.\n *\n * A front door is now an adapter: **parse and resolve, call an op, render.** The CLI\n * subcommand bodies do that with flags, a config file and an exit code; the MCP tools\n * do it with a JSON Schema and a result envelope. What each may still keep is what is\n * genuinely its own — its error type, its exit code, its wording, and any policy it\n * deliberately does not share. (`smelt retrieve` refuses a memory store; the MCP\n * server accepts one and says so when it bites. That divergence is intentional, it is\n * documented on both sides, and it lives in the adapters — never here.)\n */\n\nexport {\n budgetFault,\n budgetMalformed,\n budgetRequired,\n openStore,\n readBlob,\n readTree,\n resolveStrategy,\n} from './inputs.ts';\nexport type {\n BudgetFault,\n BudgetNaming,\n ResolvedStrategy,\n Ruling,\n StrategySource,\n TreeNaming,\n} from './inputs.ts';\nexport {\n mapTree,\n readCounters,\n readLedger,\n retrieveBytes,\n retrieveMany,\n smeltBlob,\n} from './verbs.ts';\nexport type {\n FocusSource,\n MapTreeOp,\n ReadCountersOp,\n ReadLedgerOp,\n ResolvedFocus,\n RetrieveBytesOp,\n RetrieveManyOp,\n SmeltBlobOp,\n SmeltBlobOutcome,\n} from './verbs.ts';\n"]}
@@ -1,6 +1,6 @@
1
1
  import type { RepoMap } from '../repomap/map.ts';
2
2
  import type { Strategy } from '../plan/planners.ts';
3
- import type { DetectedLanguage, ElisionStore, RetrieveStats, SmeltResult } from '../types.ts';
3
+ import type { DetectedLanguage, ElisionStore, RetrievedBlock, RetrieveStats, RuleLedgerEntry, SmeltResult } from '../types.ts';
4
4
  /**
5
5
  * The four verbs, as library functions over already-resolved inputs.
6
6
  *
@@ -45,6 +45,21 @@ export interface SmeltBlobOp {
45
45
  readonly language?: DetectedLanguage;
46
46
  /** What the task is about. Empty and absent mean the same thing to every planner. */
47
47
  readonly focus?: readonly string[];
48
+ /**
49
+ * The command whose output this blob is — `grep -C 3 foo src` — when the front door
50
+ * knows it. Used only when `focus` names nothing: the terms are derived by
51
+ * `focusTermsFor`, the same zero-import derivation the hooks guard uses for its
52
+ * rewrite wrap, so the guard, the CLI and the tool cannot disagree about which terms
53
+ * a command names. A producer that states no term (`cat`, a diff) derives none.
54
+ */
55
+ readonly producer?: string;
56
+ }
57
+ /** Where a run's focus came from, so a report can attribute it. */
58
+ export type FocusSource = 'caller' | 'producer' | 'none';
59
+ /** The focus a run actually planned with, and whose it was. */
60
+ export interface ResolvedFocus {
61
+ readonly terms: readonly string[];
62
+ readonly source: FocusSource;
48
63
  }
49
64
  /**
50
65
  * What one smelt run produced.
@@ -66,6 +81,11 @@ export interface SmeltBlobOutcome {
66
81
  readonly inputText: string;
67
82
  /** The store the run actually used — the one passed in, or the library's default. */
68
83
  readonly store: ElisionStore;
84
+ /**
85
+ * The focus the planner saw — the caller's terms, else the ones derived from
86
+ * `producer`, else none — with its source, so a report says whose terms cut.
87
+ */
88
+ readonly focus: ResolvedFocus;
69
89
  }
70
90
  /**
71
91
  * Verb: **cut one blob to a budget.**
@@ -120,6 +140,29 @@ export interface RetrieveBytesOp {
120
140
  * @throws {StoreCorruptionError} for bytes that no longer hash to their name.
121
141
  */
122
142
  export declare function retrieveBytes(op: RetrieveBytesOp): string;
143
+ /** Several hashes to turn back into bytes, in one call. */
144
+ export interface RetrieveManyOp {
145
+ /** The store holding them — already opened. */
146
+ readonly store: ElisionStore;
147
+ /** The hashes exactly as the markers printed them, in the order the blocks come back. */
148
+ readonly hashes: readonly string[];
149
+ }
150
+ /**
151
+ * Verb: **the counted read, N at a time.**
152
+ *
153
+ * The batched sibling of {@link retrieveBytes}, and the reason it is a verb of its own
154
+ * is tier 4 of the bench: every retrieval is a new request, input tokens are billed
155
+ * per request, and on five of nine cases the smelted arm's *summed* input exceeded the
156
+ * raw arm's because each one-hash call re-billed the transcript. One call for N blocks
157
+ * changes what an expansion costs, and deliberately nothing about what it *means*:
158
+ * the loop calls `store.retrieve` per hash, so each hit and each miss journals exactly
159
+ * as a single call would, and the expansion rate reads the same either way.
160
+ *
161
+ * A refusal rides inside its block rather than failing the batch — the model that
162
+ * asked for eighteen blobs and typo'd one still gets the seventeen, and the one refusal
163
+ * is the store's own distinct error. An empty list is an empty answer, and moves nothing.
164
+ */
165
+ export declare function retrieveMany(op: RetrieveManyOp): readonly RetrievedBlock[];
123
166
  /** One store to read the counters off. */
124
167
  export interface ReadCountersOp {
125
168
  readonly store: ElisionStore;
@@ -133,4 +176,17 @@ export interface ReadCountersOp {
133
176
  * dishonest.
134
177
  */
135
178
  export declare function readCounters(op: ReadCountersOp): RetrieveStats;
179
+ /** One store to read the ledger off. */
180
+ export interface ReadLedgerOp {
181
+ readonly store: ElisionStore;
182
+ }
183
+ /**
184
+ * Verb: **the uncounted read, per rule.**
185
+ *
186
+ * The sibling of {@link readCounters}: which rule cut what, and how much of it was
187
+ * asked back — the feedback loop closed as data. `undefined` when the store keeps no
188
+ * ledger, never an invented empty list: a front door that printed `[]` for a store
189
+ * that cannot know would be stating a measurement nobody made.
190
+ */
191
+ export declare function readLedger(op: ReadLedgerOp): readonly RuleLedgerEntry[] | undefined;
136
192
  //# sourceMappingURL=verbs.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"verbs.d.ts","sourceRoot":"","sources":["../../src/ops/verbs.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAEjD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,EAAE,gBAAgB,EAAE,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE9F;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,yCAAyC;AACzC,MAAM,WAAW,WAAW;IAC1B,oFAAoF;IACpF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,2EAA2E;IAC3E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,sEAAsE;IACtE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,8EAA8E;IAC9E,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B;;;;OAIG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,YAAY,CAAC;IAC9B,qFAAqF;IACrF,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,gEAAgE;IAChE,QAAQ,CAAC,QAAQ,CAAC,EAAE,gBAAgB,CAAC;IACrC,qFAAqF;IACrF,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACpC;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,8EAA8E;IAC9E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,4EAA4E;IAC5E,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,uCAAuC;IACvC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,qFAAqF;IACrF,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;CAC9B;AAED;;;;;;;;;GASG;AACH,wBAAsB,SAAS,CAAC,EAAE,EAAE,WAAW,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAkB1E;AAED,uCAAuC;AACvC,MAAM,WAAW,SAAS;IACxB,2EAA2E;IAC3E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,gEAAgE;IAChE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,sDAAsD;IACtD,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,6DAA6D;IAC7D,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;;;GAOG;AACH,wBAAgB,OAAO,CAAC,EAAE,EAAE,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,CAQvD;AAED,wCAAwC;AACxC,MAAM,WAAW,eAAe;IAC9B,mFAAmF;IACnF,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,iFAAiF;IACjF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAAC,EAAE,EAAE,eAAe,GAAG,MAAM,CAEzD;AAED,0CAA0C;AAC1C,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;CAC9B;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,EAAE,EAAE,cAAc,GAAG,aAAa,CAE9D","sourcesContent":["import { buildRepoMap } from '../repomap/map.ts';\nimport type { RepoMap } from '../repomap/map.ts';\nimport { createSmelter } from '../smelter.ts';\nimport type { Strategy } from '../plan/planners.ts';\nimport type { DetectedLanguage, ElisionStore, RetrieveStats, SmeltResult } from '../types.ts';\n\n/**\n * The four verbs, as library functions over already-resolved inputs.\n *\n * This is the seam that sits **below both front doors**. The `smelt` CLI and the\n * `@smeltjs/mcp` server are the same four operations wearing two different coats, and\n * before this file existed each of them owned a full copy of the middle: build a\n * smelter from a strategy and a store, call it, hand the totals to a report; stat a\n * directory, build a map, render its warnings; retrieve one hash; read the counters.\n * The library exported the pieces, so both copies were *correct* — and both had to be\n * edited, in two packages, for one change to how a verb runs.\n *\n * What an op takes is already resolved: a budget that survived its law, a strategy\n * that won its precedence, a store that has been opened, text that has been read. What\n * an op returns is data — the smelted text and the values a report needs, a\n * {@link RepoMap}, bytes, counters. An op never sees argv, never writes to a stream,\n * never returns an exit code, and knows nothing about `CallToolResult`. Those are the\n * adapters' four jobs, and keeping them out here is what makes the same operation\n * testable once instead of twice.\n *\n * The division of labour, in one line: **a front door parses and resolves, an op runs,\n * a front door renders.**\n */\n\n/** One blob to smelt, fully resolved. */\nexport interface SmeltBlobOp {\n /** The text itself. Reading a file into it is the front door's job (`readBlob`). */\n readonly text: string;\n /** What to call the input in a report: a path, `'<stdin>'`, `'<text>'`. */\n readonly source: string;\n /** UTF-8 bytes. Already past the budget law — see `ops/inputs.ts`. */\n readonly budgetBytes: number;\n /** Already past its precedence — see `resolveStrategy` in `ops/inputs.ts`. */\n readonly strategy: Strategy;\n /**\n * Where elided bytes go. Optional only so a caller with nothing to persist gets the\n * library's own default (a fresh {@link MemoryElisionStore}); a front door that read\n * a config opens the store it decided on and passes it.\n */\n readonly store?: ElisionStore;\n /** The path the text came from, for language detection. Absent for a pasted blob. */\n readonly path?: string;\n /** Overrides detection entirely, when a surface offers that. */\n readonly language?: DetectedLanguage;\n /** What the task is about. Empty and absent mean the same thing to every planner. */\n readonly focus?: readonly string[];\n}\n\n/**\n * What one smelt run produced.\n *\n * The fields a report needs are the fields `formatReport` takes, deliberately: an op\n * returns exactly the values the render step reads off, so no front door assembles a\n * report input by hand and no front door counts a byte itself. (`formatReport(outcome)`\n * typechecks as written.) The store comes back too, because the `--json` envelope has\n * to `peek` at the bytes the run just stored, and a caller that passed no store never\n * had a handle on the one the library made.\n */\nexport interface SmeltBlobOutcome {\n readonly result: SmeltResult;\n /** What the input was called. Echoed back so a report never re-derives it. */\n readonly source: string;\n /** The budget the run was given, so a report can say when it was missed. */\n readonly budgetBytes: number;\n /** The exact text that was smelted. */\n readonly inputText: string;\n /** The store the run actually used — the one passed in, or the library's default. */\n readonly store: ElisionStore;\n}\n\n/**\n * Verb: **cut one blob to a budget.**\n *\n * Every refusal the library can raise passes straight through — a `structural` run on\n * a language with no bundled grammar throws `GrammarUnavailableError` here exactly as\n * it does anywhere else, because a planner that quietly fell back to line windows\n * would be undetectable from outside. Coming back *over budget* is not a refusal and\n * not an error: the plan is returned as it came back, and whether that deserves a\n * non-zero exit code is a question only a front door can answer.\n */\nexport async function smeltBlob(op: SmeltBlobOp): Promise<SmeltBlobOutcome> {\n const smelter = createSmelter({\n strategy: op.strategy,\n ...(op.store === undefined ? {} : { store: op.store }),\n });\n const result = await smelter.smelt(op.text, {\n budgetBytes: op.budgetBytes,\n ...(op.path === undefined ? {} : { path: op.path }),\n ...(op.language === undefined ? {} : { language: op.language }),\n ...(op.focus === undefined || op.focus.length === 0 ? {} : { focus: op.focus }),\n });\n return {\n result,\n source: op.source,\n budgetBytes: op.budgetBytes,\n inputText: op.text,\n store: smelter.store,\n };\n}\n\n/** One tree to map, fully resolved. */\nexport interface MapTreeOp {\n /** A directory. Proving it is one is the front door's job (`readTree`). */\n readonly root: string;\n /** UTF-8 bytes. The map fits itself to this by construction. */\n readonly budgetBytes: number;\n readonly focus?: readonly string[];\n /** Replaces the built-in ignore list when present. */\n readonly ignore?: readonly string[];\n /** Only when present does the map write anything to disk. */\n readonly cacheDir?: string;\n}\n\n/**\n * Verb: **map a whole tree inside a budget.**\n *\n * Returns the {@link RepoMap} itself — the map *is* the data, warnings and cache\n * counts included, and wrapping it in an outcome struct would only give two front\n * doors a second place to disagree about what a map is. Nothing is elided, stored or\n * reversible, so there is nothing here to retrieve and no over-budget case to report.\n */\nexport function mapTree(op: MapTreeOp): Promise<RepoMap> {\n return buildRepoMap({\n root: op.root,\n budgetBytes: op.budgetBytes,\n ...(op.focus === undefined || op.focus.length === 0 ? {} : { focus: op.focus }),\n ...(op.ignore === undefined || op.ignore.length === 0 ? {} : { ignore: op.ignore }),\n ...(op.cacheDir === undefined ? {} : { cacheDir: op.cacheDir }),\n });\n}\n\n/** One hash to turn back into bytes. */\nexport interface RetrieveBytesOp {\n /** The store holding them — already opened. See `openStore` in `ops/inputs.ts`. */\n readonly store: ElisionStore;\n /** The hash exactly as a marker printed it. Validated by the store, not here. */\n readonly hash: string;\n}\n\n/**\n * Verb: **the counted read.**\n *\n * Thin on purpose, and named anyway: this is the expansion rate moving. `retrieve()`\n * journals the hit, so a shell user typing `smelt retrieve` and a model calling\n * `smelt_retrieve` move the same counter through the same call — which is the only\n * reason the number means anything. The exact original bytes come back, nothing\n * appended and nothing re-encoded, and an unknown or damaged hash throws the store's\n * own distinct error rather than an empty string.\n *\n * @throws {UnknownHashError} for a hash the store never held.\n * @throws {StoreCorruptionError} for bytes that no longer hash to their name.\n */\nexport function retrieveBytes(op: RetrieveBytesOp): string {\n return op.store.retrieve(op.hash);\n}\n\n/** One store to read the counters off. */\nexport interface ReadCountersOp {\n readonly store: ElisionStore;\n}\n\n/**\n * Verb: **the uncounted read.**\n *\n * The sibling of {@link retrieveBytes}, and the asymmetry is the point: `stats()`\n * folds the journal without writing to it, so watching the expansion rate can never\n * move it. An observer that inflated its own metric would make the honest signal\n * dishonest.\n */\nexport function readCounters(op: ReadCountersOp): RetrieveStats {\n return op.store.stats();\n}\n"]}
1
+ {"version":3,"file":"verbs.d.ts","sourceRoot":"","sources":["../../src/ops/verbs.ts"],"names":[],"mappings":"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","sourcesContent":["import { buildRepoMap } from '../repomap/map.ts';\nimport type { RepoMap } from '../repomap/map.ts';\nimport { focusTermsFor } from '../hooks/focus-terms.ts';\nimport { retrieveEach } from '../retrieve.ts';\nimport { createSmelter } from '../smelter.ts';\nimport type { Strategy } from '../plan/planners.ts';\nimport type {\n DetectedLanguage,\n ElisionStore,\n RetrievedBlock,\n RetrieveStats,\n RuleLedgerEntry,\n SmeltResult,\n} from '../types.ts';\n\n/**\n * The four verbs, as library functions over already-resolved inputs.\n *\n * This is the seam that sits **below both front doors**. The `smelt` CLI and the\n * `@smeltjs/mcp` server are the same four operations wearing two different coats, and\n * before this file existed each of them owned a full copy of the middle: build a\n * smelter from a strategy and a store, call it, hand the totals to a report; stat a\n * directory, build a map, render its warnings; retrieve one hash; read the counters.\n * The library exported the pieces, so both copies were *correct* — and both had to be\n * edited, in two packages, for one change to how a verb runs.\n *\n * What an op takes is already resolved: a budget that survived its law, a strategy\n * that won its precedence, a store that has been opened, text that has been read. What\n * an op returns is data — the smelted text and the values a report needs, a\n * {@link RepoMap}, bytes, counters. An op never sees argv, never writes to a stream,\n * never returns an exit code, and knows nothing about `CallToolResult`. Those are the\n * adapters' four jobs, and keeping them out here is what makes the same operation\n * testable once instead of twice.\n *\n * The division of labour, in one line: **a front door parses and resolves, an op runs,\n * a front door renders.**\n */\n\n/** One blob to smelt, fully resolved. */\nexport interface SmeltBlobOp {\n /** The text itself. Reading a file into it is the front door's job (`readBlob`). */\n readonly text: string;\n /** What to call the input in a report: a path, `'<stdin>'`, `'<text>'`. */\n readonly source: string;\n /** UTF-8 bytes. Already past the budget law — see `ops/inputs.ts`. */\n readonly budgetBytes: number;\n /** Already past its precedence — see `resolveStrategy` in `ops/inputs.ts`. */\n readonly strategy: Strategy;\n /**\n * Where elided bytes go. Optional only so a caller with nothing to persist gets the\n * library's own default (a fresh {@link MemoryElisionStore}); a front door that read\n * a config opens the store it decided on and passes it.\n */\n readonly store?: ElisionStore;\n /** The path the text came from, for language detection. Absent for a pasted blob. */\n readonly path?: string;\n /** Overrides detection entirely, when a surface offers that. */\n readonly language?: DetectedLanguage;\n /** What the task is about. Empty and absent mean the same thing to every planner. */\n readonly focus?: readonly string[];\n /**\n * The command whose output this blob is — `grep -C 3 foo src` — when the front door\n * knows it. Used only when `focus` names nothing: the terms are derived by\n * `focusTermsFor`, the same zero-import derivation the hooks guard uses for its\n * rewrite wrap, so the guard, the CLI and the tool cannot disagree about which terms\n * a command names. A producer that states no term (`cat`, a diff) derives none.\n */\n readonly producer?: string;\n}\n\n/** Where a run's focus came from, so a report can attribute it. */\nexport type FocusSource = 'caller' | 'producer' | 'none';\n\n/** The focus a run actually planned with, and whose it was. */\nexport interface ResolvedFocus {\n readonly terms: readonly string[];\n readonly source: FocusSource;\n}\n\n/**\n * What one smelt run produced.\n *\n * The fields a report needs are the fields `formatReport` takes, deliberately: an op\n * returns exactly the values the render step reads off, so no front door assembles a\n * report input by hand and no front door counts a byte itself. (`formatReport(outcome)`\n * typechecks as written.) The store comes back too, because the `--json` envelope has\n * to `peek` at the bytes the run just stored, and a caller that passed no store never\n * had a handle on the one the library made.\n */\nexport interface SmeltBlobOutcome {\n readonly result: SmeltResult;\n /** What the input was called. Echoed back so a report never re-derives it. */\n readonly source: string;\n /** The budget the run was given, so a report can say when it was missed. */\n readonly budgetBytes: number;\n /** The exact text that was smelted. */\n readonly inputText: string;\n /** The store the run actually used — the one passed in, or the library's default. */\n readonly store: ElisionStore;\n /**\n * The focus the planner saw — the caller's terms, else the ones derived from\n * `producer`, else none — with its source, so a report says whose terms cut.\n */\n readonly focus: ResolvedFocus;\n}\n\n/**\n * Verb: **cut one blob to a budget.**\n *\n * Every refusal the library can raise passes straight through — a `structural` run on\n * a language with no bundled grammar throws `GrammarUnavailableError` here exactly as\n * it does anywhere else, because a planner that quietly fell back to line windows\n * would be undetectable from outside. Coming back *over budget* is not a refusal and\n * not an error: the plan is returned as it came back, and whether that deserves a\n * non-zero exit code is a question only a front door can answer.\n */\nexport async function smeltBlob(op: SmeltBlobOp): Promise<SmeltBlobOutcome> {\n const smelter = createSmelter({\n strategy: op.strategy,\n ...(op.store === undefined ? {} : { store: op.store }),\n });\n const focus = resolveFocus(op);\n const result = await smelter.smelt(op.text, {\n budgetBytes: op.budgetBytes,\n ...(op.path === undefined ? {} : { path: op.path }),\n ...(op.language === undefined ? {} : { language: op.language }),\n ...(focus.terms.length === 0 ? {} : { focus: focus.terms }),\n });\n return {\n result,\n source: op.source,\n budgetBytes: op.budgetBytes,\n inputText: op.text,\n store: smelter.store,\n focus,\n };\n}\n\n/**\n * The caller's terms win; the producer fills only what the caller left unsaid; and\n * an answer of none is reported as none rather than as an empty list nobody can\n * attribute. The precedence is one-directional on purpose — a producer hint can never\n * override a term the caller typed.\n */\nfunction resolveFocus(op: SmeltBlobOp): ResolvedFocus {\n const caller = (op.focus ?? []).filter((term) => term.length > 0);\n if (caller.length > 0) return { terms: caller, source: 'caller' };\n const derived = focusTermsFor(op.producer);\n if (derived.length > 0) return { terms: derived, source: 'producer' };\n return { terms: [], source: 'none' };\n}\n\n/** One tree to map, fully resolved. */\nexport interface MapTreeOp {\n /** A directory. Proving it is one is the front door's job (`readTree`). */\n readonly root: string;\n /** UTF-8 bytes. The map fits itself to this by construction. */\n readonly budgetBytes: number;\n readonly focus?: readonly string[];\n /** Replaces the built-in ignore list when present. */\n readonly ignore?: readonly string[];\n /** Only when present does the map write anything to disk. */\n readonly cacheDir?: string;\n}\n\n/**\n * Verb: **map a whole tree inside a budget.**\n *\n * Returns the {@link RepoMap} itself — the map *is* the data, warnings and cache\n * counts included, and wrapping it in an outcome struct would only give two front\n * doors a second place to disagree about what a map is. Nothing is elided, stored or\n * reversible, so there is nothing here to retrieve and no over-budget case to report.\n */\nexport function mapTree(op: MapTreeOp): Promise<RepoMap> {\n return buildRepoMap({\n root: op.root,\n budgetBytes: op.budgetBytes,\n ...(op.focus === undefined || op.focus.length === 0 ? {} : { focus: op.focus }),\n ...(op.ignore === undefined || op.ignore.length === 0 ? {} : { ignore: op.ignore }),\n ...(op.cacheDir === undefined ? {} : { cacheDir: op.cacheDir }),\n });\n}\n\n/** One hash to turn back into bytes. */\nexport interface RetrieveBytesOp {\n /** The store holding them — already opened. See `openStore` in `ops/inputs.ts`. */\n readonly store: ElisionStore;\n /** The hash exactly as a marker printed it. Validated by the store, not here. */\n readonly hash: string;\n}\n\n/**\n * Verb: **the counted read.**\n *\n * Thin on purpose, and named anyway: this is the expansion rate moving. `retrieve()`\n * journals the hit, so a shell user typing `smelt retrieve` and a model calling\n * `smelt_retrieve` move the same counter through the same call — which is the only\n * reason the number means anything. The exact original bytes come back, nothing\n * appended and nothing re-encoded, and an unknown or damaged hash throws the store's\n * own distinct error rather than an empty string.\n *\n * @throws {UnknownHashError} for a hash the store never held.\n * @throws {StoreCorruptionError} for bytes that no longer hash to their name.\n */\nexport function retrieveBytes(op: RetrieveBytesOp): string {\n return op.store.retrieve(op.hash);\n}\n\n/** Several hashes to turn back into bytes, in one call. */\nexport interface RetrieveManyOp {\n /** The store holding them — already opened. */\n readonly store: ElisionStore;\n /** The hashes exactly as the markers printed them, in the order the blocks come back. */\n readonly hashes: readonly string[];\n}\n\n/**\n * Verb: **the counted read, N at a time.**\n *\n * The batched sibling of {@link retrieveBytes}, and the reason it is a verb of its own\n * is tier 4 of the bench: every retrieval is a new request, input tokens are billed\n * per request, and on five of nine cases the smelted arm's *summed* input exceeded the\n * raw arm's because each one-hash call re-billed the transcript. One call for N blocks\n * changes what an expansion costs, and deliberately nothing about what it *means*:\n * the loop calls `store.retrieve` per hash, so each hit and each miss journals exactly\n * as a single call would, and the expansion rate reads the same either way.\n *\n * A refusal rides inside its block rather than failing the batch — the model that\n * asked for eighteen blobs and typo'd one still gets the seventeen, and the one refusal\n * is the store's own distinct error. An empty list is an empty answer, and moves nothing.\n */\nexport function retrieveMany(op: RetrieveManyOp): readonly RetrievedBlock[] {\n return retrieveEach(op.store, op.hashes);\n}\n\n/** One store to read the counters off. */\nexport interface ReadCountersOp {\n readonly store: ElisionStore;\n}\n\n/**\n * Verb: **the uncounted read.**\n *\n * The sibling of {@link retrieveBytes}, and the asymmetry is the point: `stats()`\n * folds the journal without writing to it, so watching the expansion rate can never\n * move it. An observer that inflated its own metric would make the honest signal\n * dishonest.\n */\nexport function readCounters(op: ReadCountersOp): RetrieveStats {\n return op.store.stats();\n}\n\n/** One store to read the ledger off. */\nexport interface ReadLedgerOp {\n readonly store: ElisionStore;\n}\n\n/**\n * Verb: **the uncounted read, per rule.**\n *\n * The sibling of {@link readCounters}: which rule cut what, and how much of it was\n * asked back — the feedback loop closed as data. `undefined` when the store keeps no\n * ledger, never an invented empty list: a front door that printed `[]` for a store\n * that cannot know would be stating a measurement nobody made.\n */\nexport function readLedger(op: ReadLedgerOp): readonly RuleLedgerEntry[] | undefined {\n return op.store.ledger?.();\n}\n"]}
package/dist/ops/verbs.js CHANGED
@@ -1,4 +1,6 @@
1
1
  import { buildRepoMap } from '../repomap/map.js';
2
+ import { focusTermsFor } from '../hooks/focus-terms.js';
3
+ import { retrieveEach } from '../retrieve.js';
2
4
  import { createSmelter } from '../smelter.js';
3
5
  /**
4
6
  * Verb: **cut one blob to a budget.**
@@ -15,11 +17,12 @@ export async function smeltBlob(op) {
15
17
  strategy: op.strategy,
16
18
  ...(op.store === undefined ? {} : { store: op.store }),
17
19
  });
20
+ const focus = resolveFocus(op);
18
21
  const result = await smelter.smelt(op.text, {
19
22
  budgetBytes: op.budgetBytes,
20
23
  ...(op.path === undefined ? {} : { path: op.path }),
21
24
  ...(op.language === undefined ? {} : { language: op.language }),
22
- ...(op.focus === undefined || op.focus.length === 0 ? {} : { focus: op.focus }),
25
+ ...(focus.terms.length === 0 ? {} : { focus: focus.terms }),
23
26
  });
24
27
  return {
25
28
  result,
@@ -27,8 +30,24 @@ export async function smeltBlob(op) {
27
30
  budgetBytes: op.budgetBytes,
28
31
  inputText: op.text,
29
32
  store: smelter.store,
33
+ focus,
30
34
  };
31
35
  }
36
+ /**
37
+ * The caller's terms win; the producer fills only what the caller left unsaid; and
38
+ * an answer of none is reported as none rather than as an empty list nobody can
39
+ * attribute. The precedence is one-directional on purpose — a producer hint can never
40
+ * override a term the caller typed.
41
+ */
42
+ function resolveFocus(op) {
43
+ const caller = (op.focus ?? []).filter((term) => term.length > 0);
44
+ if (caller.length > 0)
45
+ return { terms: caller, source: 'caller' };
46
+ const derived = focusTermsFor(op.producer);
47
+ if (derived.length > 0)
48
+ return { terms: derived, source: 'producer' };
49
+ return { terms: [], source: 'none' };
50
+ }
32
51
  /**
33
52
  * Verb: **map a whole tree inside a budget.**
34
53
  *
@@ -62,6 +81,24 @@ export function mapTree(op) {
62
81
  export function retrieveBytes(op) {
63
82
  return op.store.retrieve(op.hash);
64
83
  }
84
+ /**
85
+ * Verb: **the counted read, N at a time.**
86
+ *
87
+ * The batched sibling of {@link retrieveBytes}, and the reason it is a verb of its own
88
+ * is tier 4 of the bench: every retrieval is a new request, input tokens are billed
89
+ * per request, and on five of nine cases the smelted arm's *summed* input exceeded the
90
+ * raw arm's because each one-hash call re-billed the transcript. One call for N blocks
91
+ * changes what an expansion costs, and deliberately nothing about what it *means*:
92
+ * the loop calls `store.retrieve` per hash, so each hit and each miss journals exactly
93
+ * as a single call would, and the expansion rate reads the same either way.
94
+ *
95
+ * A refusal rides inside its block rather than failing the batch — the model that
96
+ * asked for eighteen blobs and typo'd one still gets the seventeen, and the one refusal
97
+ * is the store's own distinct error. An empty list is an empty answer, and moves nothing.
98
+ */
99
+ export function retrieveMany(op) {
100
+ return retrieveEach(op.store, op.hashes);
101
+ }
65
102
  /**
66
103
  * Verb: **the uncounted read.**
67
104
  *
@@ -73,4 +110,15 @@ export function retrieveBytes(op) {
73
110
  export function readCounters(op) {
74
111
  return op.store.stats();
75
112
  }
113
+ /**
114
+ * Verb: **the uncounted read, per rule.**
115
+ *
116
+ * The sibling of {@link readCounters}: which rule cut what, and how much of it was
117
+ * asked back — the feedback loop closed as data. `undefined` when the store keeps no
118
+ * ledger, never an invented empty list: a front door that printed `[]` for a store
119
+ * that cannot know would be stating a measurement nobody made.
120
+ */
121
+ export function readLedger(op) {
122
+ return op.store.ledger?.();
123
+ }
76
124
  //# sourceMappingURL=verbs.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"verbs.js","sourceRoot":"","sources":["../../src/ops/verbs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAyE9C;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,EAAe;IAC7C,MAAM,OAAO,GAAG,aAAa,CAAC;QAC5B,QAAQ,EAAE,EAAE,CAAC,QAAQ;QACrB,GAAG,CAAC,EAAE,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,EAAE,CAAC;KACvD,CAAC,CAAC;IACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE;QAC1C,WAAW,EAAE,EAAE,CAAC,WAAW;QAC3B,GAAG,CAAC,EAAE,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC;QACnD,GAAG,CAAC,EAAE,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,EAAE,CAAC,QAAQ,EAAE,CAAC;QAC/D,GAAG,CAAC,EAAE,CAAC,KAAK,KAAK,SAAS,IAAI,EAAE,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,EAAE,CAAC;KAChF,CAAC,CAAC;IACH,OAAO;QACL,MAAM;QACN,MAAM,EAAE,EAAE,CAAC,MAAM;QACjB,WAAW,EAAE,EAAE,CAAC,WAAW;QAC3B,SAAS,EAAE,EAAE,CAAC,IAAI;QAClB,KAAK,EAAE,OAAO,CAAC,KAAK;KACrB,CAAC;AACJ,CAAC;AAeD;;;;;;;GAOG;AACH,MAAM,UAAU,OAAO,CAAC,EAAa;IACnC,OAAO,YAAY,CAAC;QAClB,IAAI,EAAE,EAAE,CAAC,IAAI;QACb,WAAW,EAAE,EAAE,CAAC,WAAW;QAC3B,GAAG,CAAC,EAAE,CAAC,KAAK,KAAK,SAAS,IAAI,EAAE,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,EAAE,CAAC;QAC/E,GAAG,CAAC,EAAE,CAAC,MAAM,KAAK,SAAS,IAAI,EAAE,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC;QACnF,GAAG,CAAC,EAAE,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,EAAE,CAAC,QAAQ,EAAE,CAAC;KAChE,CAAC,CAAC;AACL,CAAC;AAUD;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,aAAa,CAAC,EAAmB;IAC/C,OAAO,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;AACpC,CAAC;AAOD;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAAC,EAAkB;IAC7C,OAAO,EAAE,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;AAC1B,CAAC","sourcesContent":["import { buildRepoMap } from '../repomap/map.ts';\nimport type { RepoMap } from '../repomap/map.ts';\nimport { createSmelter } from '../smelter.ts';\nimport type { Strategy } from '../plan/planners.ts';\nimport type { DetectedLanguage, ElisionStore, RetrieveStats, SmeltResult } from '../types.ts';\n\n/**\n * The four verbs, as library functions over already-resolved inputs.\n *\n * This is the seam that sits **below both front doors**. The `smelt` CLI and the\n * `@smeltjs/mcp` server are the same four operations wearing two different coats, and\n * before this file existed each of them owned a full copy of the middle: build a\n * smelter from a strategy and a store, call it, hand the totals to a report; stat a\n * directory, build a map, render its warnings; retrieve one hash; read the counters.\n * The library exported the pieces, so both copies were *correct* — and both had to be\n * edited, in two packages, for one change to how a verb runs.\n *\n * What an op takes is already resolved: a budget that survived its law, a strategy\n * that won its precedence, a store that has been opened, text that has been read. What\n * an op returns is data — the smelted text and the values a report needs, a\n * {@link RepoMap}, bytes, counters. An op never sees argv, never writes to a stream,\n * never returns an exit code, and knows nothing about `CallToolResult`. Those are the\n * adapters' four jobs, and keeping them out here is what makes the same operation\n * testable once instead of twice.\n *\n * The division of labour, in one line: **a front door parses and resolves, an op runs,\n * a front door renders.**\n */\n\n/** One blob to smelt, fully resolved. */\nexport interface SmeltBlobOp {\n /** The text itself. Reading a file into it is the front door's job (`readBlob`). */\n readonly text: string;\n /** What to call the input in a report: a path, `'<stdin>'`, `'<text>'`. */\n readonly source: string;\n /** UTF-8 bytes. Already past the budget law — see `ops/inputs.ts`. */\n readonly budgetBytes: number;\n /** Already past its precedence — see `resolveStrategy` in `ops/inputs.ts`. */\n readonly strategy: Strategy;\n /**\n * Where elided bytes go. Optional only so a caller with nothing to persist gets the\n * library's own default (a fresh {@link MemoryElisionStore}); a front door that read\n * a config opens the store it decided on and passes it.\n */\n readonly store?: ElisionStore;\n /** The path the text came from, for language detection. Absent for a pasted blob. */\n readonly path?: string;\n /** Overrides detection entirely, when a surface offers that. */\n readonly language?: DetectedLanguage;\n /** What the task is about. Empty and absent mean the same thing to every planner. */\n readonly focus?: readonly string[];\n}\n\n/**\n * What one smelt run produced.\n *\n * The fields a report needs are the fields `formatReport` takes, deliberately: an op\n * returns exactly the values the render step reads off, so no front door assembles a\n * report input by hand and no front door counts a byte itself. (`formatReport(outcome)`\n * typechecks as written.) The store comes back too, because the `--json` envelope has\n * to `peek` at the bytes the run just stored, and a caller that passed no store never\n * had a handle on the one the library made.\n */\nexport interface SmeltBlobOutcome {\n readonly result: SmeltResult;\n /** What the input was called. Echoed back so a report never re-derives it. */\n readonly source: string;\n /** The budget the run was given, so a report can say when it was missed. */\n readonly budgetBytes: number;\n /** The exact text that was smelted. */\n readonly inputText: string;\n /** The store the run actually used — the one passed in, or the library's default. */\n readonly store: ElisionStore;\n}\n\n/**\n * Verb: **cut one blob to a budget.**\n *\n * Every refusal the library can raise passes straight through — a `structural` run on\n * a language with no bundled grammar throws `GrammarUnavailableError` here exactly as\n * it does anywhere else, because a planner that quietly fell back to line windows\n * would be undetectable from outside. Coming back *over budget* is not a refusal and\n * not an error: the plan is returned as it came back, and whether that deserves a\n * non-zero exit code is a question only a front door can answer.\n */\nexport async function smeltBlob(op: SmeltBlobOp): Promise<SmeltBlobOutcome> {\n const smelter = createSmelter({\n strategy: op.strategy,\n ...(op.store === undefined ? {} : { store: op.store }),\n });\n const result = await smelter.smelt(op.text, {\n budgetBytes: op.budgetBytes,\n ...(op.path === undefined ? {} : { path: op.path }),\n ...(op.language === undefined ? {} : { language: op.language }),\n ...(op.focus === undefined || op.focus.length === 0 ? {} : { focus: op.focus }),\n });\n return {\n result,\n source: op.source,\n budgetBytes: op.budgetBytes,\n inputText: op.text,\n store: smelter.store,\n };\n}\n\n/** One tree to map, fully resolved. */\nexport interface MapTreeOp {\n /** A directory. Proving it is one is the front door's job (`readTree`). */\n readonly root: string;\n /** UTF-8 bytes. The map fits itself to this by construction. */\n readonly budgetBytes: number;\n readonly focus?: readonly string[];\n /** Replaces the built-in ignore list when present. */\n readonly ignore?: readonly string[];\n /** Only when present does the map write anything to disk. */\n readonly cacheDir?: string;\n}\n\n/**\n * Verb: **map a whole tree inside a budget.**\n *\n * Returns the {@link RepoMap} itself — the map *is* the data, warnings and cache\n * counts included, and wrapping it in an outcome struct would only give two front\n * doors a second place to disagree about what a map is. Nothing is elided, stored or\n * reversible, so there is nothing here to retrieve and no over-budget case to report.\n */\nexport function mapTree(op: MapTreeOp): Promise<RepoMap> {\n return buildRepoMap({\n root: op.root,\n budgetBytes: op.budgetBytes,\n ...(op.focus === undefined || op.focus.length === 0 ? {} : { focus: op.focus }),\n ...(op.ignore === undefined || op.ignore.length === 0 ? {} : { ignore: op.ignore }),\n ...(op.cacheDir === undefined ? {} : { cacheDir: op.cacheDir }),\n });\n}\n\n/** One hash to turn back into bytes. */\nexport interface RetrieveBytesOp {\n /** The store holding them — already opened. See `openStore` in `ops/inputs.ts`. */\n readonly store: ElisionStore;\n /** The hash exactly as a marker printed it. Validated by the store, not here. */\n readonly hash: string;\n}\n\n/**\n * Verb: **the counted read.**\n *\n * Thin on purpose, and named anyway: this is the expansion rate moving. `retrieve()`\n * journals the hit, so a shell user typing `smelt retrieve` and a model calling\n * `smelt_retrieve` move the same counter through the same call — which is the only\n * reason the number means anything. The exact original bytes come back, nothing\n * appended and nothing re-encoded, and an unknown or damaged hash throws the store's\n * own distinct error rather than an empty string.\n *\n * @throws {UnknownHashError} for a hash the store never held.\n * @throws {StoreCorruptionError} for bytes that no longer hash to their name.\n */\nexport function retrieveBytes(op: RetrieveBytesOp): string {\n return op.store.retrieve(op.hash);\n}\n\n/** One store to read the counters off. */\nexport interface ReadCountersOp {\n readonly store: ElisionStore;\n}\n\n/**\n * Verb: **the uncounted read.**\n *\n * The sibling of {@link retrieveBytes}, and the asymmetry is the point: `stats()`\n * folds the journal without writing to it, so watching the expansion rate can never\n * move it. An observer that inflated its own metric would make the honest signal\n * dishonest.\n */\nexport function readCounters(op: ReadCountersOp): RetrieveStats {\n return op.store.stats();\n}\n"]}
1
+ {"version":3,"file":"verbs.js","sourceRoot":"","sources":["../../src/ops/verbs.ts"],"names":[],"mappings":"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","sourcesContent":["import { buildRepoMap } from '../repomap/map.ts';\nimport type { RepoMap } from '../repomap/map.ts';\nimport { focusTermsFor } from '../hooks/focus-terms.ts';\nimport { retrieveEach } from '../retrieve.ts';\nimport { createSmelter } from '../smelter.ts';\nimport type { Strategy } from '../plan/planners.ts';\nimport type {\n DetectedLanguage,\n ElisionStore,\n RetrievedBlock,\n RetrieveStats,\n RuleLedgerEntry,\n SmeltResult,\n} from '../types.ts';\n\n/**\n * The four verbs, as library functions over already-resolved inputs.\n *\n * This is the seam that sits **below both front doors**. The `smelt` CLI and the\n * `@smeltjs/mcp` server are the same four operations wearing two different coats, and\n * before this file existed each of them owned a full copy of the middle: build a\n * smelter from a strategy and a store, call it, hand the totals to a report; stat a\n * directory, build a map, render its warnings; retrieve one hash; read the counters.\n * The library exported the pieces, so both copies were *correct* — and both had to be\n * edited, in two packages, for one change to how a verb runs.\n *\n * What an op takes is already resolved: a budget that survived its law, a strategy\n * that won its precedence, a store that has been opened, text that has been read. What\n * an op returns is data — the smelted text and the values a report needs, a\n * {@link RepoMap}, bytes, counters. An op never sees argv, never writes to a stream,\n * never returns an exit code, and knows nothing about `CallToolResult`. Those are the\n * adapters' four jobs, and keeping them out here is what makes the same operation\n * testable once instead of twice.\n *\n * The division of labour, in one line: **a front door parses and resolves, an op runs,\n * a front door renders.**\n */\n\n/** One blob to smelt, fully resolved. */\nexport interface SmeltBlobOp {\n /** The text itself. Reading a file into it is the front door's job (`readBlob`). */\n readonly text: string;\n /** What to call the input in a report: a path, `'<stdin>'`, `'<text>'`. */\n readonly source: string;\n /** UTF-8 bytes. Already past the budget law — see `ops/inputs.ts`. */\n readonly budgetBytes: number;\n /** Already past its precedence — see `resolveStrategy` in `ops/inputs.ts`. */\n readonly strategy: Strategy;\n /**\n * Where elided bytes go. Optional only so a caller with nothing to persist gets the\n * library's own default (a fresh {@link MemoryElisionStore}); a front door that read\n * a config opens the store it decided on and passes it.\n */\n readonly store?: ElisionStore;\n /** The path the text came from, for language detection. Absent for a pasted blob. */\n readonly path?: string;\n /** Overrides detection entirely, when a surface offers that. */\n readonly language?: DetectedLanguage;\n /** What the task is about. Empty and absent mean the same thing to every planner. */\n readonly focus?: readonly string[];\n /**\n * The command whose output this blob is — `grep -C 3 foo src` — when the front door\n * knows it. Used only when `focus` names nothing: the terms are derived by\n * `focusTermsFor`, the same zero-import derivation the hooks guard uses for its\n * rewrite wrap, so the guard, the CLI and the tool cannot disagree about which terms\n * a command names. A producer that states no term (`cat`, a diff) derives none.\n */\n readonly producer?: string;\n}\n\n/** Where a run's focus came from, so a report can attribute it. */\nexport type FocusSource = 'caller' | 'producer' | 'none';\n\n/** The focus a run actually planned with, and whose it was. */\nexport interface ResolvedFocus {\n readonly terms: readonly string[];\n readonly source: FocusSource;\n}\n\n/**\n * What one smelt run produced.\n *\n * The fields a report needs are the fields `formatReport` takes, deliberately: an op\n * returns exactly the values the render step reads off, so no front door assembles a\n * report input by hand and no front door counts a byte itself. (`formatReport(outcome)`\n * typechecks as written.) The store comes back too, because the `--json` envelope has\n * to `peek` at the bytes the run just stored, and a caller that passed no store never\n * had a handle on the one the library made.\n */\nexport interface SmeltBlobOutcome {\n readonly result: SmeltResult;\n /** What the input was called. Echoed back so a report never re-derives it. */\n readonly source: string;\n /** The budget the run was given, so a report can say when it was missed. */\n readonly budgetBytes: number;\n /** The exact text that was smelted. */\n readonly inputText: string;\n /** The store the run actually used — the one passed in, or the library's default. */\n readonly store: ElisionStore;\n /**\n * The focus the planner saw — the caller's terms, else the ones derived from\n * `producer`, else none — with its source, so a report says whose terms cut.\n */\n readonly focus: ResolvedFocus;\n}\n\n/**\n * Verb: **cut one blob to a budget.**\n *\n * Every refusal the library can raise passes straight through — a `structural` run on\n * a language with no bundled grammar throws `GrammarUnavailableError` here exactly as\n * it does anywhere else, because a planner that quietly fell back to line windows\n * would be undetectable from outside. Coming back *over budget* is not a refusal and\n * not an error: the plan is returned as it came back, and whether that deserves a\n * non-zero exit code is a question only a front door can answer.\n */\nexport async function smeltBlob(op: SmeltBlobOp): Promise<SmeltBlobOutcome> {\n const smelter = createSmelter({\n strategy: op.strategy,\n ...(op.store === undefined ? {} : { store: op.store }),\n });\n const focus = resolveFocus(op);\n const result = await smelter.smelt(op.text, {\n budgetBytes: op.budgetBytes,\n ...(op.path === undefined ? {} : { path: op.path }),\n ...(op.language === undefined ? {} : { language: op.language }),\n ...(focus.terms.length === 0 ? {} : { focus: focus.terms }),\n });\n return {\n result,\n source: op.source,\n budgetBytes: op.budgetBytes,\n inputText: op.text,\n store: smelter.store,\n focus,\n };\n}\n\n/**\n * The caller's terms win; the producer fills only what the caller left unsaid; and\n * an answer of none is reported as none rather than as an empty list nobody can\n * attribute. The precedence is one-directional on purpose — a producer hint can never\n * override a term the caller typed.\n */\nfunction resolveFocus(op: SmeltBlobOp): ResolvedFocus {\n const caller = (op.focus ?? []).filter((term) => term.length > 0);\n if (caller.length > 0) return { terms: caller, source: 'caller' };\n const derived = focusTermsFor(op.producer);\n if (derived.length > 0) return { terms: derived, source: 'producer' };\n return { terms: [], source: 'none' };\n}\n\n/** One tree to map, fully resolved. */\nexport interface MapTreeOp {\n /** A directory. Proving it is one is the front door's job (`readTree`). */\n readonly root: string;\n /** UTF-8 bytes. The map fits itself to this by construction. */\n readonly budgetBytes: number;\n readonly focus?: readonly string[];\n /** Replaces the built-in ignore list when present. */\n readonly ignore?: readonly string[];\n /** Only when present does the map write anything to disk. */\n readonly cacheDir?: string;\n}\n\n/**\n * Verb: **map a whole tree inside a budget.**\n *\n * Returns the {@link RepoMap} itself — the map *is* the data, warnings and cache\n * counts included, and wrapping it in an outcome struct would only give two front\n * doors a second place to disagree about what a map is. Nothing is elided, stored or\n * reversible, so there is nothing here to retrieve and no over-budget case to report.\n */\nexport function mapTree(op: MapTreeOp): Promise<RepoMap> {\n return buildRepoMap({\n root: op.root,\n budgetBytes: op.budgetBytes,\n ...(op.focus === undefined || op.focus.length === 0 ? {} : { focus: op.focus }),\n ...(op.ignore === undefined || op.ignore.length === 0 ? {} : { ignore: op.ignore }),\n ...(op.cacheDir === undefined ? {} : { cacheDir: op.cacheDir }),\n });\n}\n\n/** One hash to turn back into bytes. */\nexport interface RetrieveBytesOp {\n /** The store holding them — already opened. See `openStore` in `ops/inputs.ts`. */\n readonly store: ElisionStore;\n /** The hash exactly as a marker printed it. Validated by the store, not here. */\n readonly hash: string;\n}\n\n/**\n * Verb: **the counted read.**\n *\n * Thin on purpose, and named anyway: this is the expansion rate moving. `retrieve()`\n * journals the hit, so a shell user typing `smelt retrieve` and a model calling\n * `smelt_retrieve` move the same counter through the same call — which is the only\n * reason the number means anything. The exact original bytes come back, nothing\n * appended and nothing re-encoded, and an unknown or damaged hash throws the store's\n * own distinct error rather than an empty string.\n *\n * @throws {UnknownHashError} for a hash the store never held.\n * @throws {StoreCorruptionError} for bytes that no longer hash to their name.\n */\nexport function retrieveBytes(op: RetrieveBytesOp): string {\n return op.store.retrieve(op.hash);\n}\n\n/** Several hashes to turn back into bytes, in one call. */\nexport interface RetrieveManyOp {\n /** The store holding them — already opened. */\n readonly store: ElisionStore;\n /** The hashes exactly as the markers printed them, in the order the blocks come back. */\n readonly hashes: readonly string[];\n}\n\n/**\n * Verb: **the counted read, N at a time.**\n *\n * The batched sibling of {@link retrieveBytes}, and the reason it is a verb of its own\n * is tier 4 of the bench: every retrieval is a new request, input tokens are billed\n * per request, and on five of nine cases the smelted arm's *summed* input exceeded the\n * raw arm's because each one-hash call re-billed the transcript. One call for N blocks\n * changes what an expansion costs, and deliberately nothing about what it *means*:\n * the loop calls `store.retrieve` per hash, so each hit and each miss journals exactly\n * as a single call would, and the expansion rate reads the same either way.\n *\n * A refusal rides inside its block rather than failing the batch — the model that\n * asked for eighteen blobs and typo'd one still gets the seventeen, and the one refusal\n * is the store's own distinct error. An empty list is an empty answer, and moves nothing.\n */\nexport function retrieveMany(op: RetrieveManyOp): readonly RetrievedBlock[] {\n return retrieveEach(op.store, op.hashes);\n}\n\n/** One store to read the counters off. */\nexport interface ReadCountersOp {\n readonly store: ElisionStore;\n}\n\n/**\n * Verb: **the uncounted read.**\n *\n * The sibling of {@link retrieveBytes}, and the asymmetry is the point: `stats()`\n * folds the journal without writing to it, so watching the expansion rate can never\n * move it. An observer that inflated its own metric would make the honest signal\n * dishonest.\n */\nexport function readCounters(op: ReadCountersOp): RetrieveStats {\n return op.store.stats();\n}\n\n/** One store to read the ledger off. */\nexport interface ReadLedgerOp {\n readonly store: ElisionStore;\n}\n\n/**\n * Verb: **the uncounted read, per rule.**\n *\n * The sibling of {@link readCounters}: which rule cut what, and how much of it was\n * asked back — the feedback loop closed as data. `undefined` when the store keeps no\n * ledger, never an invented empty list: a front door that printed `[]` for a store\n * that cannot know would be stating a measurement nobody made.\n */\nexport function readLedger(op: ReadLedgerOp): readonly RuleLedgerEntry[] | undefined {\n return op.store.ledger?.();\n}\n"]}
@@ -1,4 +1,6 @@
1
1
  import type { ElisionPlan, PlanInput, Planner } from '../types.ts';
2
+ import type { DiffPlannerOptions } from './diff.ts';
3
+ import type { JsonPlannerOptions } from './json.ts';
2
4
  import type { LexicalPlannerOptions } from './lexical.ts';
3
5
  import type { StructuralPlannerOptions } from './structural.ts';
4
6
  /**
@@ -16,10 +18,15 @@ export interface AutoPlannerOptions {
16
18
  readonly lexical?: LexicalPlannerOptions;
17
19
  /** Passed through when auto picks the structural planner. */
18
20
  readonly structural?: StructuralPlannerOptions;
21
+ /** Passed through when the content kind is JSON. */
22
+ readonly json?: JsonPlannerOptions;
23
+ /** Passed through when the content kind is a diff. */
24
+ readonly diff?: DiffPlannerOptions;
19
25
  }
20
26
  /**
21
- * The strategy that picks a strategy: **structural where a grammar is bundled,
22
- * lexical everywhere else, and the result says which one ran.**
27
+ * The strategy that picks a strategy: **kind first, then language json for a JSON
28
+ * document, diff for a unified diff, structural where a grammar is bundled, lexical
29
+ * everywhere else — and the result says which one ran.**
23
30
  *
24
31
  * It exists because the choice it makes is one a caller cannot make once. A consumer
25
32
  * smelting whatever a tool handed it — a `.ts` file this call, a build log the next —
@@ -1 +1 @@
1
- {"version":3,"file":"auto.d.ts","sourceRoot":"","sources":["../../src/plan/auto.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAGnE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAE1D,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,iBAAiB,CAAC;AAEhE;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,YAAY,CAAC;AAEzC,MAAM,WAAW,kBAAkB;IACjC,0DAA0D;IAC1D,QAAQ,CAAC,OAAO,CAAC,EAAE,qBAAqB,CAAC;IACzC,6DAA6D;IAC7D,QAAQ,CAAC,UAAU,CAAC,EAAE,wBAAwB,CAAC;CAChD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,qBAAa,WAAY,YAAW,OAAO;;IACzC,QAAQ,CAAC,EAAE,aAAmB;IAG9B,YAAY,OAAO,GAAE,kBAAuB,EAE3C;IAED,IAAI,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,WAAW,CAAC,CAE3C;CACF;AAED;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,GAAE,kBAAuB,GAAG,OAAO,CAAC,WAAW,CAAC,CAIjG","sourcesContent":["import type { ElisionPlan, PlanInput, Planner } from '../types.ts';\n\nimport { planLexical } from './lexical.ts';\nimport type { LexicalPlannerOptions } from './lexical.ts';\nimport { isStructuralLanguage, planStructural } from './structural.ts';\nimport type { StructuralPlannerOptions } from './structural.ts';\n\n/**\n * The id of the *selector*, never of a plan.\n *\n * Every plan `auto` returns carries the id of the planner that actually ran —\n * `lexical/v1` or `structural/v1` — because a caller reading `result.planner` is\n * asking what happened to their bytes, not which name they typed. This constant\n * exists so `PLANNERS.auto({}).id` says something true about the object it is on, and\n * `test/guards/auto-strategy.test.ts` pins that it never reaches an `ElisionPlan`.\n */\nexport const AUTO_PLANNER_ID = 'auto/v1';\n\nexport interface AutoPlannerOptions {\n /** Passed through when auto picks the lexical planner. */\n readonly lexical?: LexicalPlannerOptions;\n /** Passed through when auto picks the structural planner. */\n readonly structural?: StructuralPlannerOptions;\n}\n\n/**\n * The strategy that picks a strategy: **structural where a grammar is bundled,\n * lexical everywhere else, and the result says which one ran.**\n *\n * It exists because the choice it makes is one a caller cannot make once. A consumer\n * smelting whatever a tool handed it — a `.ts` file this call, a build log the next —\n * has to name a strategy per call or accept the wrong one every other call, and the\n * two wrong answers are not symmetric: `lexical` on TypeScript is a working planner\n * doing a weak job, while `structural` on a build log is a `GrammarUnavailableError`.\n * So the honest default for a *mixed* stream was neither name, and callers picked one\n * anyway.\n *\n * **This is a selector, not a fallback, and the distinction is the whole design.**\n *\n * - It decides on a *fact*: whether the language carries a bundled grammar\n * ({@link STRUCTURAL_LANGUAGES}). That is knowable before a byte is parsed, so\n * the decision is made up front and stated in the result's `planner` field.\n * - It never decides on an *accident*. A grammar that fails to load on a language\n * smelt claims to support is a broken install, and `GrammarUnavailableError`\n * travels straight out of here exactly as it does under `strategy: 'structural'`.\n * Catching it and answering with line windows would turn a loud environment fault\n * into quietly worse output — the failure mode Law 2's no-silent-downgrade\n * reasoning is about, wearing a friendlier name.\n * - It changes nothing about an explicit `strategy: 'structural'`. That still\n * refuses an unsupported language, because a caller who named the planner asked\n * for *its* guarantees, and a refusal is the only answer that does not fabricate\n * them. `auto` is a different request — \"pick for me\" — and answers it in the\n * open.\n *\n * `DEFAULT_STRATEGY` stays `lexical`: auto is opt-in, and a default that changed\n * which planner ran would change what existing callers' results are labelled without\n * anyone asking for it.\n */\nexport class AutoPlanner implements Planner {\n readonly id = AUTO_PLANNER_ID;\n readonly #options: AutoPlannerOptions;\n\n constructor(options: AutoPlannerOptions = {}) {\n this.#options = options;\n }\n\n plan(input: PlanInput): Promise<ElisionPlan> {\n return planAuto(input, this.#options);\n }\n}\n\n/**\n * The selector as a function, exported like {@link planLexical} and\n * {@link planStructural} so it can be tested and reused directly. Deterministic: the\n * choice is a lookup in {@link STRUCTURAL_LANGUAGES}, and each planner it delegates to\n * is deterministic in turn.\n *\n * @throws {GrammarUnavailableError} when the language *is* one smelt claims to parse\n * and its grammar cannot be loaded. Never caught here — see {@link AutoPlanner}.\n */\nexport function planAuto(input: PlanInput, options: AutoPlannerOptions = {}): Promise<ElisionPlan> {\n return isStructuralLanguage(input.language)\n ? planStructural(input, options.structural ?? {})\n : Promise.resolve(planLexical(input, options.lexical ?? {}));\n}\n"]}
1
+ {"version":3,"file":"auto.d.ts","sourceRoot":"","sources":["../../src/plan/auto.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAGnE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAEpD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAGpD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAE1D,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,iBAAiB,CAAC;AAEhE;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,YAAY,CAAC;AAEzC,MAAM,WAAW,kBAAkB;IACjC,0DAA0D;IAC1D,QAAQ,CAAC,OAAO,CAAC,EAAE,qBAAqB,CAAC;IACzC,6DAA6D;IAC7D,QAAQ,CAAC,UAAU,CAAC,EAAE,wBAAwB,CAAC;IAC/C,oDAAoD;IACpD,QAAQ,CAAC,IAAI,CAAC,EAAE,kBAAkB,CAAC;IACnC,sDAAsD;IACtD,QAAQ,CAAC,IAAI,CAAC,EAAE,kBAAkB,CAAC;CACpC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,qBAAa,WAAY,YAAW,OAAO;;IACzC,QAAQ,CAAC,EAAE,aAAmB;IAG9B,YAAY,OAAO,GAAE,kBAAuB,EAE3C;IAED,IAAI,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,WAAW,CAAC,CAE3C;CACF;AAED;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,GAAE,kBAAuB,GAAG,OAAO,CAAC,WAAW,CAAC,CAUjG","sourcesContent":["import type { ElisionPlan, PlanInput, Planner } from '../types.ts';\n\nimport { planDiff } from './diff.ts';\nimport type { DiffPlannerOptions } from './diff.ts';\nimport { planJson } from './json.ts';\nimport type { JsonPlannerOptions } from './json.ts';\nimport { probeKind } from './kind.ts';\nimport { planLexical } from './lexical.ts';\nimport type { LexicalPlannerOptions } from './lexical.ts';\nimport { isStructuralLanguage, planStructural } from './structural.ts';\nimport type { StructuralPlannerOptions } from './structural.ts';\n\n/**\n * The id of the *selector*, never of a plan.\n *\n * Every plan `auto` returns carries the id of the planner that actually ran —\n * `lexical/v1` or `structural/v1` — because a caller reading `result.planner` is\n * asking what happened to their bytes, not which name they typed. This constant\n * exists so `PLANNERS.auto({}).id` says something true about the object it is on, and\n * `test/guards/auto-strategy.test.ts` pins that it never reaches an `ElisionPlan`.\n */\nexport const AUTO_PLANNER_ID = 'auto/v1';\n\nexport interface AutoPlannerOptions {\n /** Passed through when auto picks the lexical planner. */\n readonly lexical?: LexicalPlannerOptions;\n /** Passed through when auto picks the structural planner. */\n readonly structural?: StructuralPlannerOptions;\n /** Passed through when the content kind is JSON. */\n readonly json?: JsonPlannerOptions;\n /** Passed through when the content kind is a diff. */\n readonly diff?: DiffPlannerOptions;\n}\n\n/**\n * The strategy that picks a strategy: **kind first, then language — json for a JSON\n * document, diff for a unified diff, structural where a grammar is bundled, lexical\n * everywhere else and the result says which one ran.**\n *\n * It exists because the choice it makes is one a caller cannot make once. A consumer\n * smelting whatever a tool handed it — a `.ts` file this call, a build log the next —\n * has to name a strategy per call or accept the wrong one every other call, and the\n * two wrong answers are not symmetric: `lexical` on TypeScript is a working planner\n * doing a weak job, while `structural` on a build log is a `GrammarUnavailableError`.\n * So the honest default for a *mixed* stream was neither name, and callers picked one\n * anyway.\n *\n * **This is a selector, not a fallback, and the distinction is the whole design.**\n *\n * - It decides on a *fact*: whether the language carries a bundled grammar\n * ({@link STRUCTURAL_LANGUAGES}). That is knowable before a byte is parsed, so\n * the decision is made up front and stated in the result's `planner` field.\n * - It never decides on an *accident*. A grammar that fails to load on a language\n * smelt claims to support is a broken install, and `GrammarUnavailableError`\n * travels straight out of here exactly as it does under `strategy: 'structural'`.\n * Catching it and answering with line windows would turn a loud environment fault\n * into quietly worse output — the failure mode Law 2's no-silent-downgrade\n * reasoning is about, wearing a friendlier name.\n * - It changes nothing about an explicit `strategy: 'structural'`. That still\n * refuses an unsupported language, because a caller who named the planner asked\n * for *its* guarantees, and a refusal is the only answer that does not fabricate\n * them. `auto` is a different request — \"pick for me\" — and answers it in the\n * open.\n *\n * `DEFAULT_STRATEGY` stays `lexical`: auto is opt-in, and a default that changed\n * which planner ran would change what existing callers' results are labelled without\n * anyone asking for it.\n */\nexport class AutoPlanner implements Planner {\n readonly id = AUTO_PLANNER_ID;\n readonly #options: AutoPlannerOptions;\n\n constructor(options: AutoPlannerOptions = {}) {\n this.#options = options;\n }\n\n plan(input: PlanInput): Promise<ElisionPlan> {\n return planAuto(input, this.#options);\n }\n}\n\n/**\n * The selector as a function, exported like {@link planLexical} and\n * {@link planStructural} so it can be tested and reused directly. Deterministic: the\n * choice is a lookup in {@link STRUCTURAL_LANGUAGES}, and each planner it delegates to\n * is deterministic in turn.\n *\n * @throws {GrammarUnavailableError} when the language *is* one smelt claims to parse\n * and its grammar cannot be loaded. Never caught here — see {@link AutoPlanner}.\n */\nexport function planAuto(input: PlanInput, options: AutoPlannerOptions = {}): Promise<ElisionPlan> {\n // The content kind is the first fact — a parse, a header shape (`plan/kind.ts`) —\n // because a diff is path-less and a JSON tool result detects `unknown`, and both\n // used to fall to line windows. Only what the probe *proved* is routed by kind.\n const kind = probeKind(input.text);\n if (kind === 'json') return Promise.resolve(planJson(input, options.json ?? {}));\n if (kind === 'diff') return Promise.resolve(planDiff(input, options.diff ?? {}));\n return isStructuralLanguage(input.language)\n ? planStructural(input, options.structural ?? {})\n : Promise.resolve(planLexical(input, options.lexical ?? {}));\n}\n"]}
package/dist/plan/auto.js CHANGED
@@ -1,3 +1,6 @@
1
+ import { planDiff } from './diff.js';
2
+ import { planJson } from './json.js';
3
+ import { probeKind } from './kind.js';
1
4
  import { planLexical } from './lexical.js';
2
5
  import { isStructuralLanguage, planStructural } from './structural.js';
3
6
  /**
@@ -11,8 +14,9 @@ import { isStructuralLanguage, planStructural } from './structural.js';
11
14
  */
12
15
  export const AUTO_PLANNER_ID = 'auto/v1';
13
16
  /**
14
- * The strategy that picks a strategy: **structural where a grammar is bundled,
15
- * lexical everywhere else, and the result says which one ran.**
17
+ * The strategy that picks a strategy: **kind first, then language json for a JSON
18
+ * document, diff for a unified diff, structural where a grammar is bundled, lexical
19
+ * everywhere else — and the result says which one ran.**
16
20
  *
17
21
  * It exists because the choice it makes is one a caller cannot make once. A consumer
18
22
  * smelting whatever a tool handed it — a `.ts` file this call, a build log the next —
@@ -63,6 +67,14 @@ export class AutoPlanner {
63
67
  * and its grammar cannot be loaded. Never caught here — see {@link AutoPlanner}.
64
68
  */
65
69
  export function planAuto(input, options = {}) {
70
+ // The content kind is the first fact — a parse, a header shape (`plan/kind.ts`) —
71
+ // because a diff is path-less and a JSON tool result detects `unknown`, and both
72
+ // used to fall to line windows. Only what the probe *proved* is routed by kind.
73
+ const kind = probeKind(input.text);
74
+ if (kind === 'json')
75
+ return Promise.resolve(planJson(input, options.json ?? {}));
76
+ if (kind === 'diff')
77
+ return Promise.resolve(planDiff(input, options.diff ?? {}));
66
78
  return isStructuralLanguage(input.language)
67
79
  ? planStructural(input, options.structural ?? {})
68
80
  : Promise.resolve(planLexical(input, options.lexical ?? {}));
@@ -1 +1 @@
1
- {"version":3,"file":"auto.js","sourceRoot":"","sources":["../../src/plan/auto.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAE3C,OAAO,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGvE;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,SAAS,CAAC;AASzC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,MAAM,OAAO,WAAW;IACb,EAAE,GAAG,eAAe,CAAC;IACrB,QAAQ,CAAqB;IAEtC,YAAY,OAAO,GAAuB,EAAE;QAC1C,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC;IAC1B,CAAC;IAED,IAAI,CAAC,KAAgB;QACnB,OAAO,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;IACxC,CAAC;CACF;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,QAAQ,CAAC,KAAgB,EAAE,OAAO,GAAuB,EAAE;IACzE,OAAO,oBAAoB,CAAC,KAAK,CAAC,QAAQ,CAAC;QACzC,CAAC,CAAC,cAAc,CAAC,KAAK,EAAE,OAAO,CAAC,UAAU,IAAI,EAAE,CAAC;QACjD,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,KAAK,EAAE,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,CAAC;AACjE,CAAC","sourcesContent":["import type { ElisionPlan, PlanInput, Planner } from '../types.ts';\n\nimport { planLexical } from './lexical.ts';\nimport type { LexicalPlannerOptions } from './lexical.ts';\nimport { isStructuralLanguage, planStructural } from './structural.ts';\nimport type { StructuralPlannerOptions } from './structural.ts';\n\n/**\n * The id of the *selector*, never of a plan.\n *\n * Every plan `auto` returns carries the id of the planner that actually ran —\n * `lexical/v1` or `structural/v1` — because a caller reading `result.planner` is\n * asking what happened to their bytes, not which name they typed. This constant\n * exists so `PLANNERS.auto({}).id` says something true about the object it is on, and\n * `test/guards/auto-strategy.test.ts` pins that it never reaches an `ElisionPlan`.\n */\nexport const AUTO_PLANNER_ID = 'auto/v1';\n\nexport interface AutoPlannerOptions {\n /** Passed through when auto picks the lexical planner. */\n readonly lexical?: LexicalPlannerOptions;\n /** Passed through when auto picks the structural planner. */\n readonly structural?: StructuralPlannerOptions;\n}\n\n/**\n * The strategy that picks a strategy: **structural where a grammar is bundled,\n * lexical everywhere else, and the result says which one ran.**\n *\n * It exists because the choice it makes is one a caller cannot make once. A consumer\n * smelting whatever a tool handed it — a `.ts` file this call, a build log the next —\n * has to name a strategy per call or accept the wrong one every other call, and the\n * two wrong answers are not symmetric: `lexical` on TypeScript is a working planner\n * doing a weak job, while `structural` on a build log is a `GrammarUnavailableError`.\n * So the honest default for a *mixed* stream was neither name, and callers picked one\n * anyway.\n *\n * **This is a selector, not a fallback, and the distinction is the whole design.**\n *\n * - It decides on a *fact*: whether the language carries a bundled grammar\n * ({@link STRUCTURAL_LANGUAGES}). That is knowable before a byte is parsed, so\n * the decision is made up front and stated in the result's `planner` field.\n * - It never decides on an *accident*. A grammar that fails to load on a language\n * smelt claims to support is a broken install, and `GrammarUnavailableError`\n * travels straight out of here exactly as it does under `strategy: 'structural'`.\n * Catching it and answering with line windows would turn a loud environment fault\n * into quietly worse output — the failure mode Law 2's no-silent-downgrade\n * reasoning is about, wearing a friendlier name.\n * - It changes nothing about an explicit `strategy: 'structural'`. That still\n * refuses an unsupported language, because a caller who named the planner asked\n * for *its* guarantees, and a refusal is the only answer that does not fabricate\n * them. `auto` is a different request — \"pick for me\" — and answers it in the\n * open.\n *\n * `DEFAULT_STRATEGY` stays `lexical`: auto is opt-in, and a default that changed\n * which planner ran would change what existing callers' results are labelled without\n * anyone asking for it.\n */\nexport class AutoPlanner implements Planner {\n readonly id = AUTO_PLANNER_ID;\n readonly #options: AutoPlannerOptions;\n\n constructor(options: AutoPlannerOptions = {}) {\n this.#options = options;\n }\n\n plan(input: PlanInput): Promise<ElisionPlan> {\n return planAuto(input, this.#options);\n }\n}\n\n/**\n * The selector as a function, exported like {@link planLexical} and\n * {@link planStructural} so it can be tested and reused directly. Deterministic: the\n * choice is a lookup in {@link STRUCTURAL_LANGUAGES}, and each planner it delegates to\n * is deterministic in turn.\n *\n * @throws {GrammarUnavailableError} when the language *is* one smelt claims to parse\n * and its grammar cannot be loaded. Never caught here — see {@link AutoPlanner}.\n */\nexport function planAuto(input: PlanInput, options: AutoPlannerOptions = {}): Promise<ElisionPlan> {\n return isStructuralLanguage(input.language)\n ? planStructural(input, options.structural ?? {})\n : Promise.resolve(planLexical(input, options.lexical ?? {}));\n}\n"]}
1
+ {"version":3,"file":"auto.js","sourceRoot":"","sources":["../../src/plan/auto.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAErC,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAErC,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAE3C,OAAO,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGvE;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,SAAS,CAAC;AAazC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,MAAM,OAAO,WAAW;IACb,EAAE,GAAG,eAAe,CAAC;IACrB,QAAQ,CAAqB;IAEtC,YAAY,OAAO,GAAuB,EAAE;QAC1C,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC;IAC1B,CAAC;IAED,IAAI,CAAC,KAAgB;QACnB,OAAO,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;IACxC,CAAC;CACF;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,QAAQ,CAAC,KAAgB,EAAE,OAAO,GAAuB,EAAE;IACzE,kFAAkF;IAClF,iFAAiF;IACjF,gFAAgF;IAChF,MAAM,IAAI,GAAG,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACnC,IAAI,IAAI,KAAK,MAAM;QAAE,OAAO,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC;IACjF,IAAI,IAAI,KAAK,MAAM;QAAE,OAAO,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC;IACjF,OAAO,oBAAoB,CAAC,KAAK,CAAC,QAAQ,CAAC;QACzC,CAAC,CAAC,cAAc,CAAC,KAAK,EAAE,OAAO,CAAC,UAAU,IAAI,EAAE,CAAC;QACjD,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,KAAK,EAAE,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,CAAC;AACjE,CAAC","sourcesContent":["import type { ElisionPlan, PlanInput, Planner } from '../types.ts';\n\nimport { planDiff } from './diff.ts';\nimport type { DiffPlannerOptions } from './diff.ts';\nimport { planJson } from './json.ts';\nimport type { JsonPlannerOptions } from './json.ts';\nimport { probeKind } from './kind.ts';\nimport { planLexical } from './lexical.ts';\nimport type { LexicalPlannerOptions } from './lexical.ts';\nimport { isStructuralLanguage, planStructural } from './structural.ts';\nimport type { StructuralPlannerOptions } from './structural.ts';\n\n/**\n * The id of the *selector*, never of a plan.\n *\n * Every plan `auto` returns carries the id of the planner that actually ran —\n * `lexical/v1` or `structural/v1` — because a caller reading `result.planner` is\n * asking what happened to their bytes, not which name they typed. This constant\n * exists so `PLANNERS.auto({}).id` says something true about the object it is on, and\n * `test/guards/auto-strategy.test.ts` pins that it never reaches an `ElisionPlan`.\n */\nexport const AUTO_PLANNER_ID = 'auto/v1';\n\nexport interface AutoPlannerOptions {\n /** Passed through when auto picks the lexical planner. */\n readonly lexical?: LexicalPlannerOptions;\n /** Passed through when auto picks the structural planner. */\n readonly structural?: StructuralPlannerOptions;\n /** Passed through when the content kind is JSON. */\n readonly json?: JsonPlannerOptions;\n /** Passed through when the content kind is a diff. */\n readonly diff?: DiffPlannerOptions;\n}\n\n/**\n * The strategy that picks a strategy: **kind first, then language — json for a JSON\n * document, diff for a unified diff, structural where a grammar is bundled, lexical\n * everywhere else and the result says which one ran.**\n *\n * It exists because the choice it makes is one a caller cannot make once. A consumer\n * smelting whatever a tool handed it — a `.ts` file this call, a build log the next —\n * has to name a strategy per call or accept the wrong one every other call, and the\n * two wrong answers are not symmetric: `lexical` on TypeScript is a working planner\n * doing a weak job, while `structural` on a build log is a `GrammarUnavailableError`.\n * So the honest default for a *mixed* stream was neither name, and callers picked one\n * anyway.\n *\n * **This is a selector, not a fallback, and the distinction is the whole design.**\n *\n * - It decides on a *fact*: whether the language carries a bundled grammar\n * ({@link STRUCTURAL_LANGUAGES}). That is knowable before a byte is parsed, so\n * the decision is made up front and stated in the result's `planner` field.\n * - It never decides on an *accident*. A grammar that fails to load on a language\n * smelt claims to support is a broken install, and `GrammarUnavailableError`\n * travels straight out of here exactly as it does under `strategy: 'structural'`.\n * Catching it and answering with line windows would turn a loud environment fault\n * into quietly worse output — the failure mode Law 2's no-silent-downgrade\n * reasoning is about, wearing a friendlier name.\n * - It changes nothing about an explicit `strategy: 'structural'`. That still\n * refuses an unsupported language, because a caller who named the planner asked\n * for *its* guarantees, and a refusal is the only answer that does not fabricate\n * them. `auto` is a different request — \"pick for me\" — and answers it in the\n * open.\n *\n * `DEFAULT_STRATEGY` stays `lexical`: auto is opt-in, and a default that changed\n * which planner ran would change what existing callers' results are labelled without\n * anyone asking for it.\n */\nexport class AutoPlanner implements Planner {\n readonly id = AUTO_PLANNER_ID;\n readonly #options: AutoPlannerOptions;\n\n constructor(options: AutoPlannerOptions = {}) {\n this.#options = options;\n }\n\n plan(input: PlanInput): Promise<ElisionPlan> {\n return planAuto(input, this.#options);\n }\n}\n\n/**\n * The selector as a function, exported like {@link planLexical} and\n * {@link planStructural} so it can be tested and reused directly. Deterministic: the\n * choice is a lookup in {@link STRUCTURAL_LANGUAGES}, and each planner it delegates to\n * is deterministic in turn.\n *\n * @throws {GrammarUnavailableError} when the language *is* one smelt claims to parse\n * and its grammar cannot be loaded. Never caught here — see {@link AutoPlanner}.\n */\nexport function planAuto(input: PlanInput, options: AutoPlannerOptions = {}): Promise<ElisionPlan> {\n // The content kind is the first fact — a parse, a header shape (`plan/kind.ts`) —\n // because a diff is path-less and a JSON tool result detects `unknown`, and both\n // used to fall to line windows. Only what the probe *proved* is routed by kind.\n const kind = probeKind(input.text);\n if (kind === 'json') return Promise.resolve(planJson(input, options.json ?? {}));\n if (kind === 'diff') return Promise.resolve(planDiff(input, options.diff ?? {}));\n return isStructuralLanguage(input.language)\n ? planStructural(input, options.structural ?? {})\n : Promise.resolve(planLexical(input, options.lexical ?? {}));\n}\n"]}
@@ -0,0 +1,45 @@
1
+ import type { ElisionPlan, PlanInput, Planner } from '../types.ts';
2
+ export declare const DIFF_PLANNER_ID = "diff/v1";
3
+ /**
4
+ * The three rules, coarsest first: whole files the focus never touches; hunks inside a
5
+ * file it does; and, inside a hunk it does, the line runs no match sits near.
6
+ */
7
+ export declare const FILE_COLLAPSE_RULE = "file-collapse";
8
+ export declare const HUNK_COLLAPSE_RULE = "hunk-collapse";
9
+ export declare const HUNK_WINDOW_RULE = "hunk-window";
10
+ export interface DiffPlannerOptions {
11
+ /** Focus matching is substring, case-insensitive by default. */
12
+ readonly caseSensitive?: boolean;
13
+ }
14
+ /**
15
+ * The diff planner: **files and hunks are the units, not lines.**
16
+ *
17
+ * A unified diff has a structure a line planner cannot see: files, each with a header
18
+ * and hunks. Under a focus, a file none of whose hunks carry a term collapses whole —
19
+ * one marker per run of such files, the outline naming their paths — and inside a file
20
+ * that does match, the hunks that do not collapse as a run while the header survives
21
+ * verbatim — and inside a hunk that does match, the line runs no match sits near
22
+ * collapse as a window (the lexical planner's move, confined to the hunk, with the same
23
+ * context ladder under budget pressure), so the planner never keeps more of a hunk than
24
+ * a line planner would. With no focus every file header is kept and each file's hunks
25
+ * collapse to one marker, so the survivor is the diff's table of contents. Measured on
26
+ * the bench's real diff (`git-diff`): every hunk mentioned the focus term, and without
27
+ * the window rule this planner cut nothing where lexical cut to 1516 B.
28
+ *
29
+ * Refuses text without a unified-diff header shape ({@link ContentKindError}).
30
+ */
31
+ export declare class DiffPlanner implements Planner {
32
+ #private;
33
+ readonly id = "diff/v1";
34
+ constructor(options?: DiffPlannerOptions);
35
+ plan(input: PlanInput): Promise<ElisionPlan>;
36
+ }
37
+ /**
38
+ * The synchronous core. Deterministic; every candidate priced through the input's
39
+ * {@link MarkerPricing}. Byte offsets come straight off the UTF-8 line split, so no
40
+ * conversion is needed.
41
+ *
42
+ * @throws {ContentKindError} when the text carries no unified-diff header.
43
+ */
44
+ export declare function planDiff(input: PlanInput, options?: DiffPlannerOptions): ElisionPlan;
45
+ //# sourceMappingURL=diff.d.ts.map