packages feed

seihou-okf-extension 0.13.0.0 → 0.14.0.0

raw patch · 5 files changed

+107/−12 lines, 5 filesdep ~seihou-corePVP ok

version bump matches the API change (PVP)

Dependency ranges changed: seihou-core

API changes (from Hackage documentation)

+ Seihou.OKF.Docs.Model: BaseRecipeRef :: !RecipeReference -> !Bool -> BaseRecipeRef
+ Seihou.OKF.Docs.Model: [baseRecipe] :: DocEntry -> !Maybe BaseRecipeRef
+ Seihou.OKF.Docs.Model: [reference] :: BaseRecipeRef -> !RecipeReference
+ Seihou.OKF.Docs.Model: data BaseRecipeRef
+ Seihou.OKF.Docs.Model: instance GHC.Classes.Eq Seihou.OKF.Docs.Model.BaseRecipeRef
+ Seihou.OKF.Docs.Model: instance GHC.Internal.Generics.Generic Seihou.OKF.Docs.Model.BaseRecipeRef
+ Seihou.OKF.Docs.Model: instance GHC.Internal.Show.Show Seihou.OKF.Docs.Model.BaseRecipeRef
- Seihou.OKF.Docs.Model: DocEntry :: !Text -> !DocKind -> !Maybe Text -> !Maybe Text -> ![Text] -> !FilePath -> !DocArtifact -> ![ModuleRef] -> ![EntailedRef] -> DocEntry
+ Seihou.OKF.Docs.Model: DocEntry :: !Text -> !DocKind -> !Maybe Text -> !Maybe Text -> ![Text] -> !FilePath -> !DocArtifact -> ![ModuleRef] -> !Maybe BaseRecipeRef -> ![EntailedRef] -> DocEntry

Files

seihou-okf-extension.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.0 name: seihou-okf-extension-version: 0.13.0.0+version: 0.14.0.0 synopsis: OKF documentation extension for Seihou registries description:   External Seihou extension executable for generating OKF documentation bundles@@ -60,7 +60,7 @@     lens >=5.2 && <6,     okf-core ^>=0.10.0.0,     optparse-applicative >=0.18 && <1,-    seihou-core ^>=0.13.0.0,+    seihou-core ^>=0.14.0.0,     temporary >=1.3 && <2,     text >=2.0 && <3, @@ -118,7 +118,7 @@     hspec >=2.11 && <3,     lens >=5.2 && <6,     okf-core ^>=0.10.0.0,-    seihou-core ^>=0.13.0.0,+    seihou-core ^>=0.14.0.0,     seihou-okf-extension-internal,     tasty >=1.4 && <2,     tasty-hspec >=1.2 && <2,
src/Seihou/OKF/Docs/Model.hs view
@@ -3,6 +3,7 @@     DocArtifact (..),     DocEntry (..),     ModuleRef (..),+    BaseRecipeRef (..),     EntailedRef (..),     DocModel (..),     DocLoadError (..),@@ -10,7 +11,7 @@   ) where -import Control.Lens ((&), (.~), (^.))+import Control.Lens ((%~), (&), (.~), (^.)) import Data.Generics.Labels () import Data.Text qualified as T import GHC.Generics (Generic)@@ -23,6 +24,7 @@     Module (..),     ModuleLoadError,     Recipe (..),+    RecipeReference (..),     depModuleNames,   ) import Seihou.Dhall.Eval@@ -62,6 +64,7 @@     path :: !FilePath,     artifact :: !DocArtifact,     moduleRefs :: ![ModuleRef],+    baseRecipe :: !(Maybe BaseRecipeRef),     -- | Every edge this entry\'s migrations entail, flattened across the     -- entry\'s migration edges in declaration order. Empty for every kind but     -- a blueprint, which is the only artifact that can declare entailment.@@ -69,6 +72,13 @@   }   deriving stock (Eq, Generic, Show) +-- | A descriptor-local base edge. Documentation never fetches its source.+data BaseRecipeRef = BaseRecipeRef+  { reference :: !RecipeReference,+    resolved :: !Bool+  }+  deriving stock (Eq, Generic, Show)+ data ModuleRef = ModuleRef   { name :: !T.Text,     resolved :: !Bool@@ -130,8 +140,9 @@   pure $ do     entries <- entriesResult     let moduleNames = [entry ^. #name | entry <- entries, entry ^. #kind == DocModuleKind]+        recipeNames = [entry ^. #name | entry <- entries, entry ^. #kind == DocRecipeKind]         blueprintNames = [entry ^. #name | entry <- entries, entry ^. #kind == DocBlueprintKind]-        resolvedEntries = map (resolveEntryRefs moduleNames blueprintNames) entries+        resolvedEntries = map (resolveEntryRefs moduleNames recipeNames blueprintNames) entries     Right       DocModel         { repoName = repoName,@@ -230,6 +241,9 @@       path = entry ^. #path,       artifact = artifact,       moduleRefs = refs,+      baseRecipe = case artifact of+        DocRecipeArtifact recipe -> (\ref -> BaseRecipeRef ref False) <$> recipe ^. #extends+        _ -> Nothing,       entailedRefs = entailed     } @@ -254,10 +268,11 @@ -- | The single place that decides whether a reference points inside this -- registry. Module references resolve against the registry\'s module names, -- entailed edges against its blueprint names.-resolveEntryRefs :: [T.Text] -> [T.Text] -> DocEntry -> DocEntry-resolveEntryRefs moduleNames blueprintNames entry =+resolveEntryRefs :: [T.Text] -> [T.Text] -> [T.Text] -> DocEntry -> DocEntry+resolveEntryRefs moduleNames recipeNames blueprintNames entry =   entry     & #moduleRefs .~ [ref & #resolved .~ ((ref ^. #name) `elem` moduleNames) | ref <- entry ^. #moduleRefs]+    & #baseRecipe %~ fmap (\ref -> ref & #resolved .~ ((ref ^. #reference . #recipe . #unRecipeName) `elem` recipeNames && ref ^. #reference . #source == Nothing))     & #entailedRefs .~ [ref & #resolved .~ ((ref ^. #blueprint) `elem` blueprintNames) | ref <- entry ^. #entailedRefs]  renderModuleLoadError :: ModuleLoadError -> T.Text
src/Seihou/OKF/Docs/Render.hs view
@@ -62,6 +62,7 @@     Prompt (..),     PromptGuidance (..),     Recipe (..),+    RecipeReference (..),     Removal (..),     RemovalAction (..),     RemovalStep (..),@@ -421,10 +422,14 @@         <> optionalSection "Commands" (renderCommands commands)         <> foldMap (optionalSection "Removal" . renderRemoval) removal         <> optionalSection "Migrations" (renderMigrations migrations)-    DocRecipeArtifact Recipe {modules = recipeModules, vars, prompts} ->-      [section "Composes" (renderDependencies "This recipe does not compose any modules." recipeModules)]+    DocRecipeArtifact Recipe {modules = recipeModules, vars, prompts, extends, moduleOverrides, promptOverrides} ->+      foldMap (\ref -> [section "Extends" (renderRecipeBase entry ref)]) extends+        <> [section "Composes" (renderDependencies (maybe "This recipe does not compose any modules." (const "Uses its inherited members without adding modules.") extends) recipeModules)]+        <> optionalSection "Inherited module overrides" (T.unlines ["- `" <> (override ^. #module_ . #unModuleName) <> "`" <> renderSuppliedVars (override ^. #vars) | override <- moduleOverrides])+        <> [section "Override rules" "The oldest base supplies the primary module. Child literals override ancestor literals globally by variable name; defaults remain fallbacks. Explicit module overrides replace selected keys without adding instances. Prompt overrides replace inherited questions in place; new prompts append." | extends /= Nothing]         <> optionalSection "Variables" (renderVarDecls' vars)         <> optionalSection "Prompts" (renderPrompts prompts)+        <> optionalSection "Inherited prompt overrides" (renderPrompts promptOverrides)     DocBlueprintArtifact Blueprint {prompt, vars, prompts, baseModules, files, allowedTools, migrations, launch, versionProbe} ->       [ section "Base modules" (renderDependencies "This blueprint declares no base modules." baseModules),         section "Agent prompt" (renderAgentPrompt prompt)@@ -466,6 +471,18 @@ resolvedEntailedBlueprints :: DocEntry -> [T.Text] resolvedEntailedBlueprints entry =   [ref ^. #blueprint | ref <- entry ^. #entailedRefs, ref ^. #resolved]++renderRecipeBase :: DocEntry -> RecipeReference -> T.Text+renderRecipeBase entry ref =+  let name = ref ^. #recipe . #unRecipeName+      linked = case entry ^. #baseRecipe of+        Just base | base ^. #resolved -> case conceptIdFor DocRecipeKind name of+          Right conceptId -> renderConceptLink conceptId name+          Left _ -> "`" <> name <> "`"+        _ -> "`" <> name <> "`"+      source = maybe " (declaring registry)" (\url -> " from `" <> url <> "`") (ref ^. #source)+      interval = maybe "" (\range -> "; version >= `" <> range ^. #minimum <> "` and < `" <> range ^. #before <> "`") (ref ^. #versionRange)+   in linked <> source <> interval <> ". Selected from the source's default-branch HEAD; no historical tag search."  renderDependencies :: T.Text -> [Dependency] -> T.Text renderDependencies emptyMessage [] = emptyMessage
test/Seihou/OKF/Docs/ModelSpec.hs view
@@ -5,6 +5,7 @@ import Data.List (find) import Data.Maybe (fromMaybe) import Data.Text qualified as T+import Data.Text.IO qualified as TIO import Seihou.OKF.Docs.Model import System.Directory (createDirectoryIfMissing) import System.FilePath ((</>))@@ -56,6 +57,18 @@             blueprint = requireEntry "app-blueprint" model         (recipe ^. #moduleRefs) `shouldMatchList` [ModuleRef "base" True, ModuleRef "app" True]         (blueprint ^. #moduleRefs) `shouldBe` [ModuleRef "base" True]++    it "retains an external base reference without fetching it or requiring installed members" $+      withFixtureRegistry $ \registryDir -> do+        let path = registryDir </> "recipes/app-recipe/recipe.dhall"+        TIO.writeFile path ("(" <> T.pack recipeDhall <> ") // {extends=Some {recipe=\"external\",source=Some \"https://example.invalid/base.git\",versionRange=Some {minimum=\"1\",before=\"2\"}}}")+        model <- shouldLoad registryDir+        case requireEntry "app-recipe" model ^. #baseRecipe of+          Nothing -> expectationFailure "missing base relationship"+          Just base -> do+            base ^. #resolved `shouldBe` False+            base ^. #reference . #source `shouldBe` Just "https://example.invalid/base.git"+            base ^. #reference . #recipe `shouldBe` "external"      it "resolves an entailed edge naming a blueprint in the same registry" $ do       withFixtureRegistry $ \registryDir -> do
test/Seihou/OKF/Docs/RenderSpec.hs view
@@ -1,6 +1,6 @@ module Seihou.OKF.Docs.RenderSpec (tests) where -import Control.Lens ((&), (?~), (^.))+import Control.Lens ((%~), (&), (.~), (?~), (^.)) import Data.Generics.Labels () import Data.List (sort) import Data.Map.Strict qualified as Map@@ -155,6 +155,29 @@       body <- requireBody "recipes/rich-recipe" testOptions richModel       body `shouldSatisfy` T.isInfixOf "(with `license` = `MIT`, `project.name` = `demo`)" +    it "documents an external settings-only base and explicit overrides without dangling links" $ do+      let ref = RecipeReference "external-base" (Just "https://example.test/base.git") (Just (RecipeVersionRange "1" "2"))+          model = derivedModel ref False+      (concepts, problems) <- renderOrFail testOptions model+      problems `shouldBe` []+      body <- requireBody "recipes/app-recipe" testOptions model+      body `shouldSatisfy` T.isInfixOf "## Extends"+      body `shouldSatisfy` T.isInfixOf "`external-base` from `https://example.test/base.git`"+      body `shouldSatisfy` T.isInfixOf "version >= `1` and < `2`"+      body `shouldSatisfy` T.isInfixOf "Uses its inherited members without adding modules."+      body `shouldSatisfy` T.isInfixOf "`external-member` (with `license` = `MIT`)"+      body `shouldSatisfy` T.isInfixOf "## Inherited prompt overrides"+      body `shouldNotSatisfy` T.isInfixOf "does not compose any modules"+      length concepts `shouldBe` length (wellFormedModel ^. #entries) + 1++    it "links a base known to be in the same registry" $ do+      let parent = recipeEntry & #name .~ "parent" & #path .~ "recipes/parent"+          model = derivedModel (RecipeReference "parent" Nothing Nothing) True & #entries %~ (<> [parent])+      (_, problems) <- renderOrFail testOptions model+      problems `shouldBe` []+      body <- requireBody "recipes/app-recipe" testOptions model+      body `shouldSatisfy` T.isInfixOf "[parent](/recipes/parent.md)"+     it "links an entailed edge whose blueprint is in the same registry" $ do       body <- requireBody "blueprints/rich-blueprint" testOptions richModel       body `shouldSatisfy` T.isInfixOf "Entails:"@@ -319,6 +342,7 @@       path = "modules/base",       artifact = DocModuleArtifact (silentModuleArtifact artifactDescription),       moduleRefs = [],+      baseRecipe = Nothing,       entailedRefs = []     } @@ -359,6 +383,7 @@       path = path,       artifact = DocModuleArtifact (moduleArtifact name refs),       moduleRefs = refs,+      baseRecipe = Nothing,       entailedRefs = []     } @@ -395,9 +420,13 @@               description = Just "Recipe",               modules = [simpleDep "base", simpleDep "app"],               vars = [],-              prompts = []+              prompts = [],+              extends = Nothing,+              moduleOverrides = [],+              promptOverrides = []             },       moduleRefs = [ModuleRef "base" True, ModuleRef "app" True],+      baseRecipe = Nothing,       entailedRefs = []     } @@ -428,6 +457,7 @@               versionProbe = Nothing             },       moduleRefs = [ModuleRef "base" True],+      baseRecipe = Nothing,       entailedRefs = []     } @@ -457,6 +487,7 @@               guidance = []             },       moduleRefs = [],+      baseRecipe = Nothing,       entailedRefs = []     } @@ -482,6 +513,7 @@       path = "modules/rich",       artifact = DocModuleArtifact richModule,       moduleRefs = [],+      baseRecipe = Nothing,       entailedRefs = []     } @@ -603,9 +635,13 @@                     }                 ],               vars = [],-              prompts = []+              prompts = [],+              extends = Nothing,+              moduleOverrides = [],+              promptOverrides = []             },       moduleRefs = [ModuleRef "rich" True],+      baseRecipe = Nothing,       entailedRefs = []     } @@ -622,6 +658,7 @@       moduleRefs = [],       -- One entailed edge resolves inside this registry and one does not, which       -- is the distinction the renderer has to honour.+      baseRecipe = Nothing,       entailedRefs =         [ EntailedRef {blueprint = "other-blueprint", from = "1.9.0", to = "2.0.0", resolved = True},           EntailedRef {blueprint = "kiroku", from = "1.9.0", to = "2.0.0", resolved = False}@@ -692,6 +729,7 @@               versionProbe = Nothing             },       moduleRefs = [],+      baseRecipe = Nothing,       entailedRefs = []     } @@ -736,5 +774,17 @@               launch = Nothing             },       moduleRefs = [],+      baseRecipe = Nothing,       entailedRefs = []     }++derivedModel :: RecipeReference -> Bool -> DocModel+derivedModel reference resolved = wellFormedModel & #entries %~ map derive+  where+    derive entry = case entry ^. #artifact of+      DocRecipeArtifact recipe ->+        entry+          & #artifact .~ DocRecipeArtifact (recipe & #modules .~ [] & #extends .~ Just reference & #moduleOverrides .~ [RecipeModuleOverride "external-member" (Map.singleton "license" "MIT")] & #promptOverrides .~ [Prompt "license" "Choose a license" Nothing Nothing])+          & #moduleRefs .~ []+          & #baseRecipe .~ Just (BaseRecipeRef reference resolved)+      _ -> entry