/** * Governance-artifact scaffolding utilities (mmnto/totem#1288). * * Shared helpers for the `totem proposal new` and `totem adr new` commands. * Nothing in this module carries module-level state — every helper takes * its context via arguments so the tests can exercise both the submodule * (`/.strategy/`) and the standalone (strategy-repo root) cases. */ import { type StrategyResolverConfig } from '@mmnto/totem'; export type GovernanceType = 'proposal' | 'adr'; export interface ScaffoldOptions { type: GovernanceType; title: string; cwd: string; /** * Loaded `TotemConfig` (or any object with a `strategyRoot?: string` field). * Forwarded to the strategy-root resolver so `TotemConfig.strategyRoot` * wins precedence-2 over the sibling / submodule layers (mmnto-ai/totem#1710). */ config?: StrategyResolverConfig; } export interface GovernancePaths { /** Directory that anchors the governance layout (strategy repo root or submodule root). */ rootDir: string; /** Directory that holds the NNN-prefixed artifact files. */ targetDir: string; /** On-disk template path. May not exist; caller falls back to the hardcoded default. */ templatePath: string; /** Dashboard README refreshed by `docs:inject`. */ dashboardFile: string; } /** * Best-effort `TotemConfig` load for the governance commands. Returns * `undefined` when the config is missing, unparseable, OR resolves to the * global `~/.totem/` profile — both missing and unparseable are legitimate * states for a freshly-cloned consumer repo, and a global-profile config * is intentionally NOT used as a repo-local `strategyRoot` source (that * would let one user's personal pointer leak across every repo on disk). * The strategy-root resolver still has env / sibling / submodule layers * to fall back on (mmnto-ai/totem#1710 R3 — CR R3 global-config-leak fix). * * Shared by `proposalNewCommand` and `adrNewCommand` so the load idiom + * its `// totem-context: intentional best-effort` annotation live in one * place (mmnto-ai/totem#1710 R2). */ export declare function loadGovernanceConfig(cwd: string): Promise; /** * Resolve governance paths for the current invocation. * * Two layout shapes supported: * * 1. **Standalone (cwd IS the strategy repo)** — `` itself contains * `proposals/` or `adr/` at the top level. Used when the CLI runs from * inside the strategy repo directly. Detected here in the helper before * delegating to the resolver because the strategy-root resolver answers * "where IS the strategy repo from somewhere else", not "are we INSIDE * one already." * * 2. **Resolved (cwd is in a consuming repo)** — defer to * `resolveStrategyRoot` (mmnto-ai/totem#1710). The resolver walks env → * config → sibling → submodule precedence and returns an absolute path. * Throws an actionable `TotemError` (ADR-088) when none of the layers * resolve. * * Also throws `TotemError` when cwd is not inside a git repo. */ export declare function resolveGovernancePaths(cwd: string, type: GovernanceType, config?: StrategyResolverConfig): GovernancePaths; /** * Scan `targetDir` for `NNN-slug.md` files, parse the prefix to an int, * and return `(max + 1)` zero-padded to three digits. * * Returns `'001'` when the directory is missing or contains no matching * files. Files that do not match `^(\d{3})-(.+)\.md$` are ignored so * README.md or non-padded prefixes (e.g. `42-x.md`) do not pollute the * count. Throws `TotemError` when the next id would exceed 999. */ export declare function getNextArtifactId(targetDir: string): string; /** * Build the final artifact filename from a numeric id and a raw title. * * Sanitization: lowercase, any non-alphanumeric run becomes a single hyphen, * leading/trailing hyphens are stripped. Throws `TotemError` when the * sanitized slug is empty — the error fires BEFORE any filesystem write so * the caller never strands a half-written artifact. */ export declare function formatArtifactFilename(id: string, title: string): string; /** * Sanitize a raw title for safe inclusion in the scaffolded markdown body. * * Strips C0 control characters and `DEL` (0x00-0x1F, 0x7F) — most notably * newlines, carriage returns, and tabs — then collapses any remaining run * of whitespace to a single space and trims the edges. Without this pass, * a title like `Fix\n## Fake Heading` would inject a fresh markdown block * into the scaffolded artifact, shifting document structure out of the * scaffolder's control. The `#` character itself is allowed through on * purpose: a single-line heading that contains `#` characters stays a * single h1 heading in markdown. */ export declare function sanitizeArtifactTitle(title: string): string; /** * Default proposal template. Exported so tests and callers can inspect the * baseline shape without re-deriving it. Uses ADR-091's exact heading form * (`# Proposal NNN: Title` with a SPACE separator, not a hyphen). Keep in * sync with `DEFAULT_ADR_TEMPLATE` below. */ export declare const DEFAULT_PROPOSAL_TEMPLATE = "# Proposal {{ID}}: {{TITLE}}\n\n**Status:** Draft\n**Date:** {{DATE}}\n\n## Problem Statement\n\n_Describe the problem this proposal addresses._\n\n## Proposal\n\n_Describe the proposed change._\n\n## Alternatives Considered\n\n_List alternatives and why they were rejected._\n\n## Impact\n\n_Who / what does this affect?_\n"; /** * Default ADR template. Mirrors `DEFAULT_PROPOSAL_TEMPLATE` but with the * `# ADR NNN: Title` heading form required by ADR-091. */ export declare const DEFAULT_ADR_TEMPLATE = "# ADR {{ID}}: {{TITLE}}\n\n**Status:** Draft\n**Date:** {{DATE}}\n\n## Context\n\n_Describe the architectural context and forces at play._\n\n## Decision\n\n_State the decision._\n\n## Consequences\n\n_List the consequences. Note what improves and what regresses._\n"; export interface RenderTemplateOptions { type: GovernanceType; id: string; title: string; templatePath: string; date: string; } /** * Render the artifact template with variable substitution. * * If `templatePath` exists on disk, its contents are used; otherwise the * hardcoded `DEFAULT_PROPOSAL_TEMPLATE` / `DEFAULT_ADR_TEMPLATE` string is * used. Substitutes `{{TITLE}}`, `{{DATE}}`, and `{{ID}}` globally. * * MVP variable set per spec #1288: only `{{TITLE}}` and `{{DATE}}` are * user-facing; `{{ID}}` is internal to the default templates so the NNN * number lands in the heading without the caller having to splice it. */ export declare function renderArtifactTemplate(opts: RenderTemplateOptions): string; /** * Thin injection seam so tests can verify exact argv without spawning a * real subprocess. Production callers omit the `exec` field and we default * to `safeExec`. */ export type ExecFn = (cmd: string, args: string[], cwd?: string) => void; export interface PostScaffoldHookOptions { rootDir: string; newFilePath: string; dashboardFile: string; /** Override the exec function (test seam). Defaults to `safeExec`. */ exec?: ExecFn; } export interface PostScaffoldHookResult { /** True if `pnpm run docs:inject` exited 0. False when the script is missing or failed. */ dashboardRefreshed: boolean; /** True if `git add` staged the two paths. False if git returned non-zero. */ staged: boolean; } /** * Run the two post-scaffold side-effects in sequence: * * 1. `pnpm run docs:inject` (refresh the dashboard index). On non-zero exit * or missing script, warn to stderr and continue — the scaffolded file * already exists on disk, so a dashboard refresh failure should not * strand the artifact. * 2. `git add `. Stages ONLY those two paths; * never `-A` or `.` (per lesson-8067935e / lesson-4a01b498). On failure, * warn and return `staged: false` so the caller can surface the stage * state in its user-facing summary. * * Neither step throws; both failures degrade gracefully so the user always * walks away with the new artifact on disk. */ export declare function runPostScaffoldHooks(opts: PostScaffoldHookOptions): PostScaffoldHookResult; export interface ScaffoldArtifactResult { /** Zero-padded NNN id chosen for the artifact. */ id: string; /** Basename of the new file (`NNN-kebab-title.md`). */ filename: string; /** Absolute path the file was written to. */ filePath: string; /** Absolute path to the dashboard README that `docs:inject` refreshes. */ dashboardFile: string; /** True if `pnpm run docs:inject` succeeded. */ dashboardRefreshed: boolean; /** True if `git add` staged the two paths. */ staged: boolean; } export interface ScaffoldArtifactInternals { /** Override the exec function (test seam). Defaults to `safeExec`. */ exec?: ExecFn; /** Override the date string (test seam). Defaults to today in `YYYY-MM-DD`. */ date?: string; /** * Force a specific NNN id instead of calling `getNextArtifactId`. Test-only * seam for deterministic collision scenarios; production callers must not * pass this (the auto-increment is the spec behavior). */ forceId?: string; } /** * Full scaffolding pipeline, invoked by both `totem proposal new` and * `totem adr new`: * * resolve paths → compute id → sanitize filename → render template → * collision guard → write file → run docs:inject + git add * * Pre-disk validation (path resolution, id computation, slug sanitization, * collision check) happens BEFORE the filesystem is touched so a bad input * never strands a half-written artifact. */ export declare function scaffoldGovernanceArtifact(options: ScaffoldOptions, internals?: ScaffoldArtifactInternals): ScaffoldArtifactResult; //# sourceMappingURL=governance.d.ts.map