packages feed

seihou-core-0.4.0.0: src/Seihou/Core/Scaffold.hs

module Seihou.Core.Scaffold
  ( moduleDhall,
    readmeTemplate,
    blueprintDhall,
    examplePromptMarkdown,
    promptDhall,
    exampleAgentPromptMarkdown,
  )
where

import Data.Text qualified as T
import Seihou.Prelude

-- | Generate the module.dhall content for a new module.
-- The output imports the schema via URL and uses record completion (::).
moduleDhall :: Text -> Text -> Text -> Text
moduleDhall name url hash =
  T.unlines
    [ "let S =",
      "      " <> url,
      "        " <> hash,
      "",
      "in  S.Module::{",
      "    , name = \"" <> name <> "\"",
      "    , version = Some \"0.1.0\"",
      "    , description = Some \"A new seihou module\"",
      "    , vars =",
      "      [ S.VarDecl::{",
      "        , name = \"project.name\"",
      "        , type = \"text\"",
      "        , description = Some \"The name of the project\"",
      "        , required = True",
      "        }",
      "      ]",
      "    , prompts =",
      "      [ S.Prompt::{",
      "        , var = \"project.name\"",
      "        , text = \"What is your project name?\"",
      "        }",
      "      ]",
      "    , steps =",
      "      [ S.Step::{",
      "        , strategy = \"template\"",
      "        , src = \"README.md.tpl\"",
      "        , dest = \"README.md\"",
      "        }",
      "      ]",
      "    }"
    ]

-- | Generate the README.md.tpl template content.
readmeTemplate :: Text
readmeTemplate = "# {{project.name}}\n\nA project generated by seihou.\n"

-- | Generate the blueprint.dhall content for a new blueprint.
-- The output imports the schema via URL and uses record completion (::).
-- The prompt body is kept in a sibling @prompt.md@ file so authors can
-- edit Markdown directly without escaping through Dhall's string literal
-- rules; the Dhall record imports it via @./prompt.md as Text@.
blueprintDhall :: Text -> Text -> Text -> Text
blueprintDhall name url hash =
  T.unlines
    [ "let S =",
      "      " <> url,
      "        " <> hash,
      "",
      "in  S.Blueprint::{",
      "    , name = \"" <> name <> "\"",
      "    , version = Some \"0.1.0\"",
      "    , description = Some \"A new seihou blueprint\"",
      "    , prompt = ./prompt.md as Text",
      "    , vars =",
      "      [ S.VarDecl::{",
      "        , name = \"project.name\"",
      "        , type = \"text\"",
      "        , description = Some \"The name of the project\"",
      "        , required = True",
      "        }",
      "      ]",
      "    , prompts =",
      "      [ S.Prompt::{",
      "        , var = \"project.name\"",
      "        , text = \"What is your project name?\"",
      "        }",
      "      ]",
      "    , baseModules = [] : List S.Dependency.Type",
      "    , files = [] : List S.Blueprint.BlueprintFile.Type",
      "    , tags = [] : List Text",
      "    }"
    ]

-- | Example @prompt.md@ body emitted next to a freshly scaffolded
-- @blueprint.dhall@. The body opens with a @{{project.name}}@
-- placeholder so authors can see prompt-time substitution working, then
-- describes the workflow the agent runner (EP-31) will follow at run
-- time. Authors are expected to edit this file freely; the Dhall record
-- imports it verbatim via @./prompt.md as Text@.
examplePromptMarkdown :: Text
examplePromptMarkdown =
  T.unlines
    [ "# {{project.name}}",
      "",
      "You are helping a user scaffold a new project from this blueprint.",
      "",
      "## What you have access to",
      "",
      "- The user's current working directory (where this blueprint is being",
      "  applied). Read, edit, and create files freely.",
      "- The blueprint's `files/` directory, mounted read-only as reference",
      "  material. Copy or adapt snippets, examples, or partial templates",
      "  from there as needed.",
      "- The full `seihou` and `git` toolsets. Use `seihou run`, `seihou",
      "  status`, and `seihou validate-module` to compose modules and check",
      "  state.",
      "",
      "## How to proceed",
      "",
      "1. Confirm with the user what they are trying to build. Ask clarifying",
      "   questions only when the answer would change a load-bearing decision.",
      "2. Lay down the initial scaffolding. Prefer small, testable pieces over",
      "   one large drop.",
      "3. Iterate with the user, making edits as the design takes shape.",
      "4. Use Conventional Commits (`feat:`, `fix:`, `docs:`, …) when",
      "   committing.",
      "5. Ask before destructive changes (deleting files, force-pushing,",
      "   rewriting history).",
      "",
      "Replace this body with the workflow that fits your blueprint. The",
      "more specific the prompt, the more useful the agent's first pass."
    ]

-- | Generate the prompt.dhall content for a first-class agent prompt.
-- The prompt schema may be ahead of the public pinned schema import during
-- local development, so this scaffold emits a self-contained Dhall record
-- with explicit empty-list types.
promptDhall :: Text -> Text -> Text -> Text
promptDhall name _url _hash =
  T.unlines
    [ "let Prompt =",
      "      { var : Text, text : Text, when : Optional Text, choices : Optional (List Text) }",
      "",
      "let CommandVar =",
      "      { name : Text",
      "      , run : Text",
      "      , workDir : Optional Text",
      "      , when : Optional Text",
      "      , trim : Bool",
      "      , maxBytes : Optional Natural",
      "      }",
      "",
      "let PromptFile =",
      "      { src : Text, description : Optional Text }",
      "",
      "let PromptGuidance =",
      "      { title : Text, body : Text, when : Optional Text }",
      "",
      "let Launch =",
      "      { provider : Optional Text, mode : Optional Text, model : Optional Text }",
      "",
      "in  { name = \"" <> name <> "\"",
      "    , version = Some \"0.1.0\"",
      "    , description = Some \"A reusable agent-session prompt\"",
      "    , prompt = ./prompt.md as Text",
      "    , vars =",
      "      [ { name = \"project.name\"",
      "        , type = \"text\"",
      "        , default = None Text",
      "        , description = Some \"The name of the project\"",
      "        , required = False",
      "        , validation = None Text",
      "        }",
      "      ]",
      "    , prompts = [] : List Prompt",
      "    , commandVars = [] : List CommandVar",
      "    , guidance =",
      "      [ { title = \"Repository workflow\"",
      "        , body = \"Inspect the project before editing, keep changes scoped, and run the smallest useful validation command.\"",
      "        , when = None Text",
      "        }",
      "      ]",
      "    , files = [] : List PromptFile",
      "    , allowedTools = None (List Text)",
      "    , tags = [] : List Text",
      "    , launch = None Launch",
      "    }"
    ]

-- | Example @prompt.md@ body emitted next to a freshly scaffolded
-- @prompt.dhall@. It includes one declared variable placeholder so
-- authors can verify prompt rendering through @seihou prompt run --debug@.
exampleAgentPromptMarkdown :: Text
exampleAgentPromptMarkdown =
  T.unlines
    [ "# {{project.name}}",
      "",
      "You are helping the user with a focused repository task.",
      "",
      "Ask before destructive changes such as deleting files, force-pushing,",
      "or rewriting history."
    ]