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."
]