substrata-cli 0.1.0 → 0.1.1
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.
- package/dist/bin.js +2 -2
- package/dist/{chunk-5LCVXNHI.js → chunk-UPQPNDGD.js} +130 -6
- package/dist/chunk-UPQPNDGD.js.map +1 -0
- package/dist/{chunk-5V44C5UO.js → chunk-XD2T4UBF.js} +11 -5
- package/dist/chunk-XD2T4UBF.js.map +1 -0
- package/dist/{dist-OCRLBALV.js → dist-NS4RCATY.js} +2 -2
- package/dist/index.js +2 -2
- package/package.json +2 -2
- package/dist/chunk-5LCVXNHI.js.map +0 -1
- package/dist/chunk-5V44C5UO.js.map +0 -1
- /package/dist/{dist-OCRLBALV.js.map → dist-NS4RCATY.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../core/src/errors.ts","../../core/src/paths.ts","../../core/src/ids.ts","../../core/src/markdown.ts","../../core/src/config.ts","../../core/src/redaction.ts","../../core/src/footprint.ts","../../core/src/memory.ts","../../core/src/supersede.ts","../../core/src/init.ts","../../core/src/setup/gitignore.ts","../../core/src/setup/symlink.ts","../../core/src/setup/shellrc.ts","../../core/src/setup/agents-md.ts","../../core/src/setup/hook.ts","../../core/src/setup/plan.ts","../../search/src/indexer.ts","../../search/src/schema.ts","../../search/src/sqlite.ts","../../search/src/freshness.ts","../../search/src/query.ts","../../search/src/ranking.ts"],"sourcesContent":["import type { SecretFinding } from './types';\n\n/** Base error for all Substrata failures. */\nexport class SubstrataError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'SubstrataError';\n }\n}\n\n/** Configuration is missing, malformed, or has an unsupported schema_version. */\nexport class ConfigError extends SubstrataError {\n constructor(message: string) {\n super(message);\n this.name = 'ConfigError';\n }\n}\n\n/** A footprint/memory file could not be parsed or is missing required metadata. */\nexport class ParseError extends SubstrataError {\n readonly filePath?: string;\n\n constructor(message: string, filePath?: string) {\n super(filePath ? `${message} (${filePath})` : message);\n this.name = 'ParseError';\n this.filePath = filePath;\n }\n}\n\n/** One or more secrets were detected and the write was refused. */\nexport class SecretDetectedError extends SubstrataError {\n readonly findings: SecretFinding[];\n\n constructor(findings: SecretFinding[]) {\n const detail = findings.map((f) => `${f.name} at line ${f.line}`).join(', ');\n super(`Refusing to write: ${findings.length} potential secret(s) detected: ${detail}`);\n this.name = 'SecretDetectedError';\n this.findings = findings;\n }\n}\n\n/** A requested resource (e.g. a footprint id) could not be found. */\nexport class NotFoundError extends SubstrataError {\n constructor(message: string) {\n super(message);\n this.name = 'NotFoundError';\n }\n}\n","import path from 'node:path';\n\n/**\n * Path helpers for the .substrata directory.\n *\n * Repo-relative paths are always emitted with forward slashes so footprint\n * `file_path` values and config storage paths are stable across platforms.\n * Absolute on-disk paths use the platform separator (via node:path).\n */\n\nexport const SUBSTRATA_DIRNAME = '.substrata';\n\n/** Convert any path to forward-slash form. */\nexport function toPosix(p: string): string {\n return p.split(path.sep).join('/');\n}\n\n/** Absolute path to the .substrata directory. */\nexport function substrataDir(cwd: string): string {\n return path.join(cwd, SUBSTRATA_DIRNAME);\n}\n\n/** Absolute path to config.yml. */\nexport function configPath(cwd: string): string {\n return path.join(substrataDir(cwd), 'config.yml');\n}\n\n/** Absolute path to the footprints directory. */\nexport function footprintsDir(cwd: string): string {\n return path.join(substrataDir(cwd), 'footprints');\n}\n\n/** Absolute path to the memory directory. */\nexport function memoryDir(cwd: string): string {\n return path.join(substrataDir(cwd), 'memory');\n}\n\n/** Absolute path to the templates directory. */\nexport function templatesDir(cwd: string): string {\n return path.join(substrataDir(cwd), 'templates');\n}\n\n/** Absolute path to the generated SQLite index. */\nexport function indexPath(cwd: string): string {\n return path.join(substrataDir(cwd), 'index', 'footprint.sqlite');\n}\n\n/**\n * Repo-relative footprint path of the form\n * `YYYY/MM/YYYY-MM-DD-<slug>-<suffix>.md` (forward slashes).\n */\nexport function footprintRelativePath(date: Date, slug: string, suffix: string): string {\n const yyyy = String(date.getUTCFullYear()).padStart(4, '0');\n const mm = String(date.getUTCMonth() + 1).padStart(2, '0');\n const dd = String(date.getUTCDate()).padStart(2, '0');\n const filename = `${yyyy}-${mm}-${dd}-${slug}-${suffix}.md`;\n return `${yyyy}/${mm}/${filename}`;\n}\n\n/** Absolute on-disk path for a footprint, given its repo-relative path. */\nexport function footprintFilePath(cwd: string, relativePath: string): string {\n return path.join(footprintsDir(cwd), ...relativePath.split('/'));\n}\n\n/** Repo-relative (forward-slash) path from an absolute path. */\nexport function relativeToCwd(cwd: string, absolutePath: string): string {\n return toPosix(path.relative(cwd, absolutePath));\n}\n","import { randomBytes } from 'node:crypto';\n\n/**\n * ID and filename generation for footprints. See plan §5.\n *\n * id : fp_<YYYYMMDD>_<slug_underscored>_<suffix>\n * filename : YYYY/MM/YYYY-MM-DD-<slug>-<suffix>.md\n */\n\n/** Crockford-ish base32 alphabet without confusing chars (no i/l/o/u). */\nconst SUFFIX_ALPHABET = 'abcdefghjkmnpqrstvwxyz0123456789';\nconst SUFFIX_LENGTH = 6;\n\n/**\n * Slugify a title into a lowercase, hyphenated slug suitable for filenames.\n * Non-alphanumeric runs become single hyphens; leading/trailing hyphens trimmed.\n */\nexport function slugify(title: string): string {\n const slug = title\n .normalize('NFKD')\n // strip combining diacritical marks\n .replace(/[̀-ͯ]/g, '')\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '');\n return slug || 'untitled';\n}\n\n/** Generate a random lowercase base32 suffix (default 6 chars). */\nexport function randomSuffix(length: number = SUFFIX_LENGTH): string {\n const bytes = randomBytes(length);\n let out = '';\n for (let i = 0; i < length; i++) {\n out += SUFFIX_ALPHABET[bytes[i]! % SUFFIX_ALPHABET.length];\n }\n return out;\n}\n\n/**\n * Build a footprint id from a date and slug.\n * The slug is underscored (hyphens → underscores) per the id format.\n */\nexport function generateFootprintId(date: Date, slug: string, suffix?: string): string {\n const yyyy = String(date.getUTCFullYear()).padStart(4, '0');\n const mm = String(date.getUTCMonth() + 1).padStart(2, '0');\n const dd = String(date.getUTCDate()).padStart(2, '0');\n const underscored = slug.replace(/-/g, '_');\n const sfx = suffix ?? randomSuffix();\n return `fp_${yyyy}${mm}${dd}_${underscored}_${sfx}`;\n}\n\n/** Build the repo-relative footprint filename `YYYY/MM/YYYY-MM-DD-<slug>-<suffix>.md`. */\nexport function buildFootprintFilename(date: Date, slug: string, suffix: string): string {\n const yyyy = String(date.getUTCFullYear()).padStart(4, '0');\n const mm = String(date.getUTCMonth() + 1).padStart(2, '0');\n const dd = String(date.getUTCDate()).padStart(2, '0');\n return `${yyyy}/${mm}/${yyyy}-${mm}-${dd}-${slug}-${suffix}.md`;\n}\n","import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';\n\nimport type { FootprintSections, RejectedOption } from './types';\n\n/**\n * Frontmatter + section rendering/parsing for footprint markdown bodies.\n *\n * Frontmatter uses the `yaml` package. Body sections follow the layout in\n * plan §5 (Purpose, Decisions, Rejected options, Implementation notes,\n * Commands run, Memory learned, Future agent guidance).\n */\n\nconst FRONTMATTER_RE = /^---\\r?\\n([\\s\\S]*?)\\r?\\n---\\r?\\n?([\\s\\S]*)$/;\n\nexport type ParsedFrontmatter = {\n /** Parsed YAML frontmatter object (empty object when none present). */\n frontmatter: Record<string, unknown>;\n /** Body text after the closing `---`. */\n body: string;\n};\n\n/** Split a raw markdown document into frontmatter + body. */\nexport function parseFrontmatter(raw: string): ParsedFrontmatter {\n const match = FRONTMATTER_RE.exec(raw);\n if (!match) {\n return { frontmatter: {}, body: raw };\n }\n const parsed = parseYaml(match[1]!) as unknown;\n const frontmatter =\n parsed && typeof parsed === 'object' ? (parsed as Record<string, unknown>) : {};\n return { frontmatter, body: match[2] ?? '' };\n}\n\n/** Serialize a frontmatter object and body back into a markdown document. */\nexport function serializeFrontmatter(frontmatter: Record<string, unknown>, body: string): string {\n const yaml = stringifyYaml(frontmatter, { lineWidth: 0 }).trimEnd();\n const trimmedBody = body.replace(/^\\r?\\n+/, '');\n return `---\\n${yaml}\\n---\\n\\n${trimmedBody.length > 0 ? `${trimmedBody.replace(/\\s+$/, '')}\\n` : ''}`;\n}\n\n/** Title is the first level-1 heading; returns '' if absent. */\nexport function extractTitle(body: string): string {\n const match = /^#\\s+(.+?)\\s*$/m.exec(body);\n return match ? match[1]!.trim() : '';\n}\n\n// --- Section rendering -------------------------------------------------------\n\nfunction renderListSection(heading: string, items: string[] | undefined): string | null {\n if (!items || items.length === 0) return null;\n const lines = items.map((item) => `- ${item}`);\n return `## ${heading}\\n\\n${lines.join('\\n')}`;\n}\n\nfunction renderProseSection(heading: string, text: string | undefined): string | null {\n if (!text || text.trim().length === 0) return null;\n return `## ${heading}\\n\\n${text.trim()}`;\n}\n\nfunction renderRejectedOptions(options: RejectedOption[] | undefined): string | null {\n if (!options || options.length === 0) return null;\n const blocks = options.map((o) => `### ${o.option}\\n\\n${o.reason.trim()}`);\n return `## Rejected options\\n\\n${blocks.join('\\n\\n')}`;\n}\n\nfunction renderCommands(commands: string[] | undefined): string | null {\n if (!commands || commands.length === 0) return null;\n return `## Commands run\\n\\n\\`\\`\\`bash\\n${commands.join('\\n')}\\n\\`\\`\\``;\n}\n\n/**\n * Render the full footprint body from a title and structured sections.\n * Only non-empty sections are emitted, in canonical order.\n */\nexport function renderFootprintBody(title: string, sections: FootprintSections): string {\n const parts: Array<string | null> = [\n `# ${title}`,\n renderProseSection('Purpose', sections.purpose),\n renderListSection('Decisions', sections.decisions),\n renderRejectedOptions(sections.rejectedOptions),\n renderProseSection('Implementation notes', sections.implementationNotes),\n renderCommands(sections.commandsRun),\n renderListSection('Memory learned', sections.memoryLearned),\n renderProseSection('Future agent guidance', sections.futureAgentGuidance),\n ];\n return `${parts.filter((p): p is string => p !== null).join('\\n\\n')}\\n`;\n}\n\n// --- Section parsing ---------------------------------------------------------\n\ntype RawSection = { heading: string; content: string };\n\n/** Split a body into level-2 (`##`) sections, ignoring the title and preamble. */\nfunction splitSections(body: string): RawSection[] {\n const lines = body.split(/\\r?\\n/);\n const sections: RawSection[] = [];\n let current: RawSection | null = null;\n let inFence = false;\n\n for (const line of lines) {\n if (/^```/.test(line.trim())) {\n inFence = !inFence;\n if (current) current.content += `${line}\\n`;\n continue;\n }\n const headingMatch = !inFence ? /^##\\s+(.+?)\\s*$/.exec(line) : null;\n if (headingMatch) {\n if (current) sections.push(current);\n current = { heading: headingMatch[1]!.trim(), content: '' };\n continue;\n }\n if (current) current.content += `${line}\\n`;\n }\n if (current) sections.push(current);\n return sections;\n}\n\nfunction parseListItems(content: string): string[] {\n return content\n .split(/\\r?\\n/)\n .map((l) => l.trim())\n .filter((l) => l.startsWith('- '))\n .map((l) => l.slice(2).trim())\n .filter((l) => l.length > 0);\n}\n\nfunction parseProse(content: string): string {\n return content.trim();\n}\n\nfunction parseCommands(content: string): string[] {\n const fenceMatch = /```(?:bash|sh)?\\r?\\n([\\s\\S]*?)```/.exec(content);\n const inner = fenceMatch ? fenceMatch[1]! : content;\n return inner\n .split(/\\r?\\n/)\n .map((l) => l.trim())\n .filter((l) => l.length > 0);\n}\n\nfunction parseRejectedOptions(content: string): RejectedOption[] {\n const options: RejectedOption[] = [];\n const parts = content.split(/^###\\s+/m);\n for (const part of parts) {\n const trimmed = part.trim();\n if (!trimmed) continue;\n const newlineIdx = trimmed.indexOf('\\n');\n if (newlineIdx === -1) {\n options.push({ option: trimmed, reason: '' });\n continue;\n }\n const option = trimmed.slice(0, newlineIdx).trim();\n const reason = trimmed.slice(newlineIdx + 1).trim();\n options.push({ option, reason });\n }\n return options;\n}\n\n/** Parse a footprint body into structured sections. Headings are case-insensitive. */\nexport function parseFootprintBody(body: string): FootprintSections {\n const sections: FootprintSections = {};\n for (const raw of splitSections(body)) {\n const key = raw.heading.toLowerCase();\n switch (key) {\n case 'purpose':\n sections.purpose = parseProse(raw.content);\n break;\n case 'decisions':\n sections.decisions = parseListItems(raw.content);\n break;\n case 'rejected options':\n sections.rejectedOptions = parseRejectedOptions(raw.content);\n break;\n case 'implementation notes':\n sections.implementationNotes = parseProse(raw.content);\n break;\n case 'commands run':\n sections.commandsRun = parseCommands(raw.content);\n break;\n case 'memory learned':\n sections.memoryLearned = parseListItems(raw.content);\n break;\n case 'future agent guidance':\n sections.futureAgentGuidance = parseProse(raw.content);\n break;\n default:\n break;\n }\n }\n return sections;\n}\n","import { readFile } from 'node:fs/promises';\nimport path from 'node:path';\n\nimport { parse as parseYaml, stringify as stringifyYaml } from 'yaml';\n\nimport { ConfigError } from './errors';\nimport { configPath } from './paths';\nimport type { SubstrataConfig } from './types';\n\n/**\n * Default redaction keys (plan §12). The redactor also matches kebab/snake\n * variants case-insensitively, so only the canonical forms are listed.\n */\nexport const DEFAULT_REDACTION_KEYS: string[] = [\n 'token',\n 'apiKey',\n 'api_key',\n 'authorization',\n 'password',\n 'secret',\n 'cookie',\n 'set-cookie',\n 'privateKey',\n 'accessToken',\n 'refreshToken',\n];\n\n/** Default config shape (plan §13). projectName is filled by loadConfig/init. */\nexport const defaultConfig: SubstrataConfig = {\n schema_version: 1,\n project: {\n name: 'substrata-demo',\n },\n storage: {\n footprints_dir: '.substrata/footprints',\n memory_dir: '.substrata/memory',\n index_path: '.substrata/index/footprint.sqlite',\n },\n search: {\n default_limit: 8,\n max_context_tokens: 1600,\n },\n security: {\n redact: true,\n scan_content: true,\n entropy_scan: false,\n entropy_min_length: 32,\n block_on_secret: true,\n redaction_keys: [\n 'token',\n 'apiKey',\n 'api_key',\n 'authorization',\n 'password',\n 'secret',\n 'cookie',\n 'privateKey',\n 'accessToken',\n 'refreshToken',\n ],\n },\n agent: {\n default_actor: 'unknown-agent',\n require_footprint_after_non_trivial_work: true,\n },\n};\n\nfunction isObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/** Deep-merge `override` onto `base`. Arrays and scalars from override win wholesale. */\nfunction deepMerge<T>(base: T, override: unknown): T {\n if (!isObject(base) || !isObject(override)) {\n return override === undefined ? base : (override as T);\n }\n const result: Record<string, unknown> = { ...base };\n for (const [key, value] of Object.entries(override)) {\n if (value === undefined) continue;\n const baseValue = (base as Record<string, unknown>)[key];\n if (isObject(baseValue) && isObject(value)) {\n result[key] = deepMerge(baseValue, value);\n } else {\n result[key] = value;\n }\n }\n return result as T;\n}\n\n/**\n * Load `.substrata/config.yml`, deep-merging user values over defaults.\n * Throws ConfigError if the file is missing/malformed or schema_version !== 1.\n * The default project name falls back to the cwd basename.\n */\nexport async function loadConfig(cwd: string): Promise<SubstrataConfig> {\n const file = configPath(cwd);\n let raw: string;\n try {\n raw = await readFile(file, 'utf8');\n } catch {\n throw new ConfigError(`Config not found at ${file}. Run \\`substrata init\\`.`);\n }\n\n let parsed: unknown;\n try {\n parsed = parseYaml(raw);\n } catch (err) {\n throw new ConfigError(`Failed to parse ${file}: ${(err as Error).message}`);\n }\n\n if (!isObject(parsed)) {\n throw new ConfigError(`Config at ${file} must be a YAML mapping.`);\n }\n\n if (parsed.schema_version !== 1) {\n throw new ConfigError(\n `Unsupported config schema_version: ${String(parsed.schema_version)} (expected 1).`,\n );\n }\n\n const base: SubstrataConfig = {\n ...defaultConfig,\n project: { name: path.basename(cwd) },\n };\n return deepMerge(base, parsed);\n}\n\n/** Render a config.yml file for `init`, overriding the project name. */\nexport function renderConfig(projectName: string): string {\n const config: SubstrataConfig = {\n ...defaultConfig,\n project: { name: projectName },\n };\n return stringifyYaml(config, { lineWidth: 0 });\n}\n","import type { RedactionOptions, SecretFinding } from './types';\n\n/**\n * Redaction (key-based) + content/pattern secret scanning. See plan §12.\n */\n\nexport const DEFAULT_REDACTION_KEYS: string[] = [\n 'token',\n 'apiKey',\n 'api_key',\n 'authorization',\n 'password',\n 'secret',\n 'cookie',\n 'set-cookie',\n 'privateKey',\n 'accessToken',\n 'refreshToken',\n];\n\nconst REDACTED = '[REDACTED]';\n\n/** Normalize a key for matching: lowercase, strip `-`/`_` separators. */\nfunction normalizeKey(key: string): string {\n return key.toLowerCase().replace(/[-_]/g, '');\n}\n\n/**\n * Recursively replace values whose key matches a redaction key (case-insensitive,\n * ignoring kebab/snake separators) with `[REDACTED]`. Returns a new structure;\n * the input is not mutated.\n */\nexport function redactDeep(value: unknown, options: RedactionOptions = {}): unknown {\n const keys = options.keys ?? DEFAULT_REDACTION_KEYS;\n const replacement = options.replacement ?? REDACTED;\n const normalizedKeys = new Set(keys.map(normalizeKey));\n\n const walk = (node: unknown): unknown => {\n if (Array.isArray(node)) {\n return node.map(walk);\n }\n if (node && typeof node === 'object') {\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(node as Record<string, unknown>)) {\n if (normalizedKeys.has(normalizeKey(k))) {\n out[k] = replacement;\n } else {\n out[k] = walk(v);\n }\n }\n return out;\n }\n return node;\n };\n\n return walk(value);\n}\n\n/** Content secret patterns (plan §12). */\nexport const SECRET_PATTERNS: Array<{ name: string; re: RegExp }> = [\n { name: 'aws_access_key_id', re: /\\bAKIA[0-9A-Z]{16}\\b/ },\n { name: 'github_pat', re: /\\bghp_[A-Za-z0-9]{36}\\b/ },\n { name: 'github_fine_grained', re: /\\bgithub_pat_[A-Za-z0-9_]{60,}\\b/ },\n { name: 'gitlab_pat', re: /\\bglpat-[A-Za-z0-9_-]{20}\\b/ },\n { name: 'slack_token', re: /\\bxox[baprs]-[A-Za-z0-9-]{10,}\\b/ },\n { name: 'google_api_key', re: /\\bAIza[0-9A-Za-z_-]{35}\\b/ },\n { name: 'openai_key', re: /\\bsk-[A-Za-z0-9]{20,}\\b/ },\n { name: 'anthropic_key', re: /\\bsk-ant-[A-Za-z0-9_-]{20,}\\b/ },\n {\n name: 'jwt',\n re: /\\beyJ[A-Za-z0-9_-]{10,}\\.[A-Za-z0-9_-]{10,}\\.[A-Za-z0-9_-]{10,}\\b/,\n },\n {\n name: 'private_key_block',\n re: /-----BEGIN (?:RSA |EC |OPENSSH |DSA |PGP )?PRIVATE KEY-----/,\n },\n { name: 'bearer_header', re: /\\bBearer\\s+[A-Za-z0-9._-]{20,}\\b/ },\n { name: 'url_basic_auth', re: /\\b[a-z][a-z0-9+.-]*:\\/\\/[^/\\s:@]+:[^/\\s:@]+@/i },\n];\n\n/**\n * Scan text for secrets, returning findings with 1-based line numbers.\n * Each pattern may match multiple times across lines.\n */\nexport function scanForSecrets(text: string): SecretFinding[] {\n const findings: SecretFinding[] = [];\n const lines = text.split(/\\r?\\n/);\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i]!;\n for (const { name, re } of SECRET_PATTERNS) {\n // Use a fresh non-global regex test per line to avoid lastIndex state.\n if (re.test(line)) {\n findings.push({ name, line: i + 1 });\n }\n }\n }\n return findings;\n}\n\n/**\n * Replace secret-pattern matches in text with `[REDACTED:<name>]`.\n * Returns the redacted text; does not detect entropy-based secrets.\n */\nexport function redactText(text: string): string {\n let out = text;\n for (const { name, re } of SECRET_PATTERNS) {\n const globalRe = new RegExp(re.source, re.flags.includes('g') ? re.flags : `${re.flags}g`);\n out = out.replace(globalRe, `[REDACTED:${name}]`);\n }\n return out;\n}\n\n/**\n * Optional high-entropy heuristic, off by default. Flags long standalone tokens\n * whose Shannon entropy exceeds a threshold. Gated by `options.enabled`.\n */\nexport function scanForHighEntropy(\n text: string,\n options: { enabled?: boolean; minLength?: number; minEntropy?: number } = {},\n): SecretFinding[] {\n if (!options.enabled) return [];\n const minLength = options.minLength ?? 32;\n const minEntropy = options.minEntropy ?? 4.0;\n const findings: SecretFinding[] = [];\n const lines = text.split(/\\r?\\n/);\n for (let i = 0; i < lines.length; i++) {\n const tokens = lines[i]!.split(/[\\s\"'`(),;]+/);\n for (const token of tokens) {\n if (token.length >= minLength && shannonEntropy(token) >= minEntropy) {\n findings.push({ name: 'high_entropy', line: i + 1 });\n break;\n }\n }\n }\n return findings;\n}\n\nfunction shannonEntropy(s: string): number {\n const counts = new Map<string, number>();\n for (const ch of s) counts.set(ch, (counts.get(ch) ?? 0) + 1);\n let entropy = 0;\n for (const count of counts.values()) {\n const p = count / s.length;\n entropy -= p * Math.log2(p);\n }\n return entropy;\n}\n","import type { Dirent } from 'node:fs';\nimport { mkdir, readdir, readFile, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\n\nimport { loadConfig } from './config';\nimport { ParseError, SecretDetectedError } from './errors';\nimport { buildFootprintFilename, generateFootprintId, randomSuffix, slugify } from './ids';\nimport {\n extractTitle,\n parseFootprintBody,\n parseFrontmatter,\n renderFootprintBody,\n serializeFrontmatter,\n} from './markdown';\nimport { footprintsDir, relativeToCwd } from './paths';\nimport { redactDeep, scanForSecrets } from './redaction';\nimport type {\n Footprint,\n FootprintFrontmatter,\n FootprintRelated,\n FootprintStatus,\n SecretFinding,\n WorkType,\n WriteFootprintInput,\n} from './types';\n\nconst REQUIRED_KEYS = [\n 'schema_version',\n 'id',\n 'created_at',\n 'actor',\n 'work_type',\n 'status',\n] as const;\n\nconst VALID_WORK_TYPES: ReadonlySet<string> = new Set<WorkType>([\n 'implementation',\n 'implementation_decision',\n 'bug_fix',\n 'refactor',\n 'investigation',\n 'architecture_decision',\n 'test_update',\n 'documentation',\n]);\n\nconst VALID_STATUSES: ReadonlySet<string> = new Set<FootprintStatus>([\n 'draft',\n 'completed',\n 'superseded',\n 'deprecated',\n]);\n\nfunction validateFrontmatter(fm: Record<string, unknown>, filePath?: string): FootprintFrontmatter {\n for (const key of REQUIRED_KEYS) {\n if (fm[key] === undefined || fm[key] === null) {\n throw new ParseError(`Footprint missing required frontmatter field \"${key}\"`, filePath);\n }\n }\n if (fm.schema_version !== 1) {\n throw new ParseError(\n `Footprint has unsupported schema_version: ${String(fm.schema_version)} (expected 1)`,\n filePath,\n );\n }\n if (typeof fm.id !== 'string' || fm.id.length === 0) {\n throw new ParseError('Footprint \"id\" must be a non-empty string', filePath);\n }\n if (typeof fm.actor !== 'string' || fm.actor.length === 0) {\n throw new ParseError('Footprint \"actor\" must be a non-empty string', filePath);\n }\n if (typeof fm.created_at !== 'string' || fm.created_at.length === 0) {\n throw new ParseError('Footprint \"created_at\" must be a non-empty string', filePath);\n }\n if (typeof fm.work_type !== 'string' || !VALID_WORK_TYPES.has(fm.work_type)) {\n throw new ParseError(`Footprint has invalid work_type: ${String(fm.work_type)}`, filePath);\n }\n if (typeof fm.status !== 'string' || !VALID_STATUSES.has(fm.status)) {\n throw new ParseError(`Footprint has invalid status: ${String(fm.status)}`, filePath);\n }\n return fm as FootprintFrontmatter;\n}\n\n/** Parse raw markdown into a Footprint, validating required frontmatter. */\nexport function parseFootprint(raw: string, filePath: string): Footprint {\n const { frontmatter, body } = parseFrontmatter(raw);\n const fm = validateFrontmatter(frontmatter, filePath);\n const title = extractTitle(body);\n const sections = parseFootprintBody(body);\n return { frontmatter: fm, title, body, sections, filePath, raw };\n}\n\n/** Read and parse a footprint file from disk. */\nexport async function parseFootprintFile(filePath: string): Promise<Footprint> {\n let raw: string;\n try {\n raw = await readFile(filePath, 'utf8');\n } catch {\n throw new ParseError('Footprint file could not be read', filePath);\n }\n return parseFootprint(raw, filePath);\n}\n\nfunction mergeSupersedes(\n related: FootprintRelated | undefined,\n supersedes: string[] | undefined,\n): FootprintRelated | undefined {\n if (!related && (!supersedes || supersedes.length === 0)) return undefined;\n const merged: FootprintRelated = { ...(related ?? {}) };\n if (supersedes && supersedes.length > 0) {\n const existing = merged.supersedes ?? [];\n merged.supersedes = Array.from(new Set([...existing, ...supersedes]));\n }\n return merged;\n}\n\n/** Drop undefined/empty fields so YAML frontmatter stays clean. */\nfunction cleanFrontmatter(fm: FootprintFrontmatter): Record<string, unknown> {\n const out: Record<string, unknown> = {\n schema_version: fm.schema_version,\n id: fm.id,\n created_at: fm.created_at,\n };\n if (fm.updated_at) out.updated_at = fm.updated_at;\n out.actor = fm.actor;\n if (fm.requester) out.requester = fm.requester;\n if (fm.agent_model) out.agent_model = fm.agent_model;\n out.work_type = fm.work_type;\n out.status = fm.status;\n if (fm.repo && (fm.repo.name || fm.repo.branch)) out.repo = fm.repo;\n if (fm.related && Object.keys(fm.related).length > 0) out.related = fm.related;\n if (fm.files_touched && fm.files_touched.length > 0) out.files_touched = fm.files_touched;\n if (fm.tags && fm.tags.length > 0) out.tags = fm.tags;\n return out;\n}\n\n/**\n * `files_touched` entries land in committed frontmatter and are matched against\n * repo paths by search — only plain repo-relative paths are meaningful, and\n * absolute or `..`-traversing entries are likely mistakes (or mischief).\n */\nfunction validateFilesTouched(files: string[] | undefined): string[] | undefined {\n if (!files || files.length === 0) return undefined;\n return files.map((entry) => {\n // Normalize separators explicitly (not via toPosix): entries are plain\n // strings that may carry Windows separators regardless of host platform.\n const f = entry.trim().replace(/\\\\/g, '/');\n if (f === '') {\n throw new ParseError('files_touched entries must be non-empty repo-relative paths');\n }\n if (f.startsWith('/') || /^[A-Za-z]:\\//.test(f)) {\n throw new ParseError(`files_touched must be repo-relative, got absolute path: ${f}`);\n }\n if (f.split('/').includes('..')) {\n throw new ParseError(`files_touched must not contain '..' segments: ${f}`);\n }\n return f;\n });\n}\n\nfunction validateRelatedUrls(related: FootprintRelated | undefined): void {\n for (const u of related?.urls ?? []) {\n let parsed: URL;\n try {\n parsed = new URL(u);\n } catch {\n throw new ParseError(`related.urls entry is not a valid URL: ${u}`);\n }\n if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {\n throw new ParseError(`related.urls only allows http(s) URLs, got: ${u}`);\n }\n }\n}\n\n/**\n * Write a new footprint: resolve config, generate id/filename, render markdown,\n * run redaction + secret scan, write the file, and return the parsed Footprint.\n * Throws SecretDetectedError when secrets remain and block_on_secret is set\n * (unless `allowSecret` is true).\n */\nexport async function writeFootprint(\n input: WriteFootprintInput & { cwd: string },\n): Promise<Footprint> {\n const { cwd } = input;\n const config = await loadConfig(cwd);\n\n const createdAt = input.createdAt ?? new Date().toISOString();\n const date = new Date(createdAt);\n const slug = slugify(input.title);\n const suffix = randomSuffix();\n const id = generateFootprintId(date, slug, suffix);\n const relPath = buildFootprintFilename(date, slug, suffix);\n const absPath = path.join(footprintsDir(cwd), ...relPath.split('/'));\n\n const workType: WorkType = input.workType ?? 'implementation';\n const status: FootprintStatus = input.status ?? 'completed';\n\n const filesTouched = validateFilesTouched(input.filesTouched);\n\n // Key-based redaction (plan §12) applies to structured frontmatter values\n // (e.g. nested objects under repo/related). It does not touch prose.\n const related = mergeSupersedes(input.related, input.supersedes);\n validateRelatedUrls(related);\n const frontmatter: FootprintFrontmatter = {\n schema_version: 1,\n id,\n created_at: createdAt,\n actor: input.actor,\n requester: input.requester,\n agent_model: input.agentModel,\n work_type: workType,\n status,\n repo: input.repo\n ? (redactDeep(input.repo, { keys: config.security.redaction_keys }) as typeof input.repo)\n : undefined,\n related: related\n ? (redactDeep(related, { keys: config.security.redaction_keys }) as typeof related)\n : undefined,\n files_touched: filesTouched,\n tags: input.tags,\n };\n\n // Render the body from structured sections.\n const body = renderFootprintBody(input.title, {\n purpose: input.purpose,\n decisions: input.decisions,\n rejectedOptions: input.rejectedOptions,\n implementationNotes: input.implementationNotes,\n commandsRun: input.commandsRun,\n memoryLearned: input.memoryLearned,\n futureAgentGuidance: input.futureAgentGuidance,\n });\n\n // Content (pattern) scan over the rendered body. Key-based redaction does not\n // mask prose secrets, so any remaining match blocks the write (plan §12).\n if (config.security.scan_content) {\n const findings: SecretFinding[] = scanForSecrets(body);\n if (findings.length > 0 && config.security.block_on_secret && !input.allowSecret) {\n throw new SecretDetectedError(findings);\n }\n }\n\n const raw = serializeFrontmatter(cleanFrontmatter(frontmatter), body);\n\n await mkdir(path.dirname(absPath), { recursive: true });\n await writeFile(absPath, raw, 'utf8');\n\n return parseFootprint(raw, absPath);\n}\n\nasync function walkMarkdown(dir: string): Promise<string[]> {\n let entries: Dirent[];\n try {\n entries = await readdir(dir, { withFileTypes: true });\n } catch {\n return [];\n }\n const files: string[] = [];\n for (const entry of entries) {\n const full = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n files.push(...(await walkMarkdown(full)));\n } else if (entry.isFile() && entry.name.endsWith('.md')) {\n files.push(full);\n }\n }\n return files;\n}\n\n/** List all footprints under the footprints dir, sorted by created_at desc. */\nexport async function listFootprints(cwd: string): Promise<Footprint[]> {\n const dir = footprintsDir(cwd);\n const files = await walkMarkdown(dir);\n const footprints = await Promise.all(files.map((f) => parseFootprintFile(f)));\n footprints.sort((a, b) => {\n const at = a.frontmatter.created_at;\n const bt = b.frontmatter.created_at;\n return at < bt ? 1 : at > bt ? -1 : 0;\n });\n return footprints;\n}\n\n/** Find a footprint by id; returns null if not present. */\nexport async function findFootprintById(cwd: string, id: string): Promise<Footprint | null> {\n const files = await walkMarkdown(footprintsDir(cwd));\n for (const file of files) {\n const fp = await parseFootprintFile(file);\n if (fp.frontmatter.id === id) return fp;\n }\n return null;\n}\n\n/** Compute the repo-relative (forward-slash) path of a footprint. */\nexport function footprintRepoPath(cwd: string, fp: Footprint): string {\n return relativeToCwd(cwd, fp.filePath);\n}\n","import type { Dirent } from 'node:fs';\nimport { readdir, readFile, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\n\nimport { ParseError } from './errors';\nimport { extractTitle, parseFrontmatter } from './markdown';\nimport { memoryDir } from './paths';\nimport type { MemoryDocument, MemoryFrontmatter } from './types';\n\n/**\n * Curated memory parsing + append-friendly entry insertion. See plan §6.\n *\n * Entries live between stable markers:\n * <!-- substrata:entries:start -->\n * ... entries ...\n * <!-- substrata:entries:end -->\n *\n * Each entry is wrapped in:\n * <!-- substrata:entry id=<sourceId> -->\n * <lines>\n * <!-- /substrata:entry -->\n */\n\nconst ENTRIES_START = '<!-- substrata:entries:start -->';\nconst ENTRIES_END = '<!-- substrata:entries:end -->';\n\nfunction entryOpen(id: string): string {\n return `<!-- substrata:entry id=${id} -->`;\n}\nconst ENTRY_CLOSE = '<!-- /substrata:entry -->';\n\nfunction validateMemoryFrontmatter(\n fm: Record<string, unknown>,\n filePath: string,\n): MemoryFrontmatter {\n if (fm.schema_version !== 1) {\n throw new ParseError(\n `Memory file has unsupported schema_version: ${String(fm.schema_version)} (expected 1)`,\n filePath,\n );\n }\n if (typeof fm.id !== 'string' || fm.id.length === 0) {\n throw new ParseError('Memory \"id\" must be a non-empty string', filePath);\n }\n return fm as MemoryFrontmatter;\n}\n\n/** Parse raw markdown into a MemoryDocument. */\nexport function parseMemory(raw: string, filePath: string): MemoryDocument {\n const { frontmatter, body } = parseFrontmatter(raw);\n const fm = validateMemoryFrontmatter(frontmatter, filePath);\n return { frontmatter: fm, title: extractTitle(body), body, filePath, raw };\n}\n\n/** Read and parse a memory file from disk. */\nexport async function parseMemoryFile(filePath: string): Promise<MemoryDocument> {\n let raw: string;\n try {\n raw = await readFile(filePath, 'utf8');\n } catch {\n throw new ParseError('Memory file could not be read', filePath);\n }\n return parseMemory(raw, filePath);\n}\n\nasync function walkMarkdown(dir: string): Promise<string[]> {\n let entries: Dirent[];\n try {\n entries = await readdir(dir, { withFileTypes: true });\n } catch {\n return [];\n }\n const files: string[] = [];\n for (const entry of entries) {\n const full = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n files.push(...(await walkMarkdown(full)));\n } else if (entry.isFile() && entry.name.endsWith('.md')) {\n files.push(full);\n }\n }\n return files;\n}\n\n/** List all memory documents under the memory dir, sorted by file path. */\nexport async function listMemoryDocuments(cwd: string): Promise<MemoryDocument[]> {\n const files = await walkMarkdown(memoryDir(cwd));\n files.sort();\n return Promise.all(files.map((f) => parseMemoryFile(f)));\n}\n\nexport type MemoryEntry = {\n sourceId: string;\n lines: string[];\n};\n\n/** Extract the set of sourceIds already present in a memory file body. */\nexport function existingEntryIds(content: string): Set<string> {\n const ids = new Set<string>();\n const re = /<!--\\s*substrata:entry\\s+id=([^\\s]+)\\s*-->/g;\n let match: RegExpExecArray | null;\n while ((match = re.exec(content)) !== null) {\n ids.add(match[1]!);\n }\n return ids;\n}\n\nfunction renderEntry(entry: MemoryEntry): string {\n return [entryOpen(entry.sourceId), ...entry.lines, ENTRY_CLOSE].join('\\n');\n}\n\n/**\n * Append entries to a memory file before the entries:end marker, creating the\n * marker block if absent. Existing content is preserved byte-for-byte; entries\n * whose sourceId already appears in the file are skipped (idempotent).\n * Returns true if the file was changed.\n */\nexport async function appendMemoryEntries(\n filePath: string,\n entries: MemoryEntry[],\n): Promise<boolean> {\n const original = await readFile(filePath, 'utf8');\n const existing = existingEntryIds(original);\n const toAdd = entries.filter((e) => !existing.has(e.sourceId));\n if (toAdd.length === 0) return false;\n\n const rendered = toAdd.map(renderEntry).join('\\n');\n\n let next: string;\n const endIdx = original.indexOf(ENTRIES_END);\n if (endIdx !== -1) {\n // Insert before the existing end marker, preserving everything around it.\n const before = original.slice(0, endIdx);\n const after = original.slice(endIdx);\n const sep = before.endsWith('\\n') ? '' : '\\n';\n next = `${before}${sep}${rendered}\\n${after}`;\n } else {\n // No marker block: create one at the end of the file.\n const base = original.endsWith('\\n') ? original : `${original}\\n`;\n next = `${base}\\n${ENTRIES_START}\\n${rendered}\\n${ENTRIES_END}\\n`;\n }\n\n await writeFile(filePath, next, 'utf8');\n return true;\n}\n","import { readFile, writeFile } from 'node:fs/promises';\n\nimport { parse as parseYaml, stringify as stringifyYaml } from 'yaml';\n\nimport { NotFoundError } from './errors';\nimport { findFootprintById } from './footprint';\nimport type { FootprintRelated } from './types';\n\n/**\n * Supersede relationship editing. See plan §5/§8.9.\n *\n * Edits ONLY the YAML frontmatter of both files (old + new); the markdown body\n * is preserved byte-for-byte. Old footprint gets status=superseded and\n * superseded_by += newId; new footprint gets supersedes += oldId.\n */\n\nconst FRONTMATTER_RE = /^(---\\r?\\n)([\\s\\S]*?)(\\r?\\n---\\r?\\n?)([\\s\\S]*)$/;\n\nfunction addUnique(list: string[] | undefined, value: string): string[] {\n const arr = list ? [...list] : [];\n if (!arr.includes(value)) arr.push(value);\n return arr;\n}\n\n/**\n * Rewrite a footprint file's frontmatter via `mutate`, preserving the exact\n * body bytes and the original frontmatter fences/newlines.\n */\nasync function editFrontmatter(\n filePath: string,\n mutate: (fm: Record<string, unknown>) => void,\n): Promise<void> {\n const raw = await readFile(filePath, 'utf8');\n const match = FRONTMATTER_RE.exec(raw);\n if (!match) {\n throw new NotFoundError(`Footprint at ${filePath} has no parseable frontmatter`);\n }\n const [, open, yamlText, close, body] = match;\n const parsed = parseYaml(yamlText!) as Record<string, unknown>;\n mutate(parsed);\n const newYaml = stringifyYaml(parsed, { lineWidth: 0 }).replace(/\\n$/, '');\n const next = `${open}${newYaml}${close}${body}`;\n await writeFile(filePath, next, 'utf8');\n}\n\n/**\n * Mark `oldId` as superseded by `newId`. Edits frontmatter only on both files.\n * Throws NotFoundError if either id cannot be located.\n */\nexport async function supersedeFootprint(cwd: string, oldId: string, newId: string): Promise<void> {\n const oldFp = await findFootprintById(cwd, oldId);\n if (!oldFp) throw new NotFoundError(`Footprint not found: ${oldId}`);\n const newFp = await findFootprintById(cwd, newId);\n if (!newFp) throw new NotFoundError(`Footprint not found: ${newId}`);\n\n await editFrontmatter(oldFp.filePath, (fm) => {\n fm.status = 'superseded';\n const related = (fm.related as FootprintRelated | undefined) ?? {};\n related.superseded_by = addUnique(related.superseded_by, newId);\n fm.related = related;\n });\n\n await editFrontmatter(newFp.filePath, (fm) => {\n const related = (fm.related as FootprintRelated | undefined) ?? {};\n related.supersedes = addUnique(related.supersedes, oldId);\n fm.related = related;\n });\n}\n","import { mkdir, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\n\nimport { renderConfig } from './config';\nimport { configPath, footprintsDir, memoryDir, substrataDir, templatesDir } from './paths';\nimport type { ChangeResult, InitOptions } from './types';\n\n/**\n * Project scaffolding for `substrata init`. Idempotent: existing files are never\n * overwritten. See plan §8.1 (\"Created on apply\") and §14.\n */\n\nconst README_CONTENTS = `# Substrata\n\nThis directory holds shared agent memory for this repository.\n\n- \\`footprints/\\` — committed source-of-truth records of agent-assisted work.\n- \\`memory/\\` — curated, durable repo knowledge agents should read often.\n- \\`templates/\\` — templates used when creating footprints and memory files.\n- \\`config.yml\\` — Substrata configuration (see the docs).\n- \\`index/\\` and \\`cache/\\` — generated, gitignored; safe to delete and rebuild.\n\nFootprints and memory files are committed. Do not store secrets, credentials,\nprivate keys, tokens, or sensitive user data here.\n`;\n\nconst FOOTPRINT_TEMPLATE = `---\nschema_version: 1\nid: fp_YYYYMMDD_slug_suffix\ncreated_at: 2026-01-01T00:00:00Z\nactor: unknown-agent\nwork_type: implementation\nstatus: completed\n---\n\n# Title\n\n## Purpose\n\nWhy this work was done.\n\n## Decisions\n\n- Decision one.\n\n## Rejected options\n\n### Option name\n\nWhy it was rejected.\n\n## Implementation notes\n\nWhat was changed.\n\n## Commands run\n\n\\`\\`\\`bash\npnpm test\n\\`\\`\\`\n\n## Memory learned\n\n- Something durable about this repo.\n\n## Future agent guidance\n\nWhat a future agent should check before changing this area.\n`;\n\nconst MEMORY_TEMPLATE = `---\nschema_version: 1\nid: mem_example\ntype: repo_conventions\ntags:\n - conventions\n---\n\n# Memory title\n\n## Section\n\n- Durable note one.\n\n<!-- substrata:entries:start -->\n<!-- substrata:entries:end -->\n`;\n\ntype FileSpec = { absPath: string; contents: string };\ntype DirSpec = string;\n\nasync function exists(p: string): Promise<boolean> {\n const { stat } = await import('node:fs/promises');\n try {\n await stat(p);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Create the .substrata scaffold. Never overwrites existing files/dirs.\n * Returns the list of changes applied (create/skip per path).\n */\nexport async function initProject(cwd: string, options: InitOptions = {}): Promise<ChangeResult[]> {\n const projectName = options.projectName ?? path.basename(path.resolve(cwd));\n\n const dirs: DirSpec[] = [\n substrataDir(cwd),\n footprintsDir(cwd),\n memoryDir(cwd),\n templatesDir(cwd),\n ];\n\n const files: FileSpec[] = [\n { absPath: configPath(cwd), contents: renderConfig(projectName) },\n { absPath: path.join(substrataDir(cwd), 'README.md'), contents: README_CONTENTS },\n { absPath: path.join(templatesDir(cwd), 'footprint.md'), contents: FOOTPRINT_TEMPLATE },\n { absPath: path.join(templatesDir(cwd), 'memory.md'), contents: MEMORY_TEMPLATE },\n ];\n\n const changes: ChangeResult[] = [];\n\n for (const dir of dirs) {\n await mkdir(dir, { recursive: true });\n }\n\n for (const file of files) {\n if (await exists(file.absPath)) {\n changes.push({\n path: file.absPath,\n action: 'skip',\n description: 'already exists',\n });\n continue;\n }\n await writeFile(file.absPath, file.contents, 'utf8');\n changes.push({\n path: file.absPath,\n action: 'create',\n description: 'created',\n contents: file.contents,\n });\n }\n\n return changes;\n}\n","import { readFileSync, writeFileSync } from 'node:fs';\nimport path from 'node:path';\n\nimport type { ChangeResult } from '../types';\nimport { isSymlink } from './symlink';\n\n/**\n * Ensure `.gitignore` ignores Substrata's generated directories without\n * duplicating lines. Pure + dry-runnable.\n */\n\nexport const GITIGNORE_LINES = ['.substrata/index/', '.substrata/cache/', '.substrata/tmp/'];\n\nfunction readIfExists(filePath: string): string | null {\n try {\n return readFileSync(filePath, 'utf8');\n } catch {\n return null;\n }\n}\n\nexport function ensureGitignore(cwd: string, dry: boolean = false): ChangeResult {\n const filePath = path.join(cwd, '.gitignore');\n if (isSymlink(filePath)) {\n return { path: filePath, action: 'skip', description: 'refused: .gitignore is a symlink' };\n }\n const existing = readIfExists(filePath);\n const present = existing ?? '';\n const presentLines = new Set(present.split(/\\r?\\n/).map((l) => l.trim()));\n\n const missing = GITIGNORE_LINES.filter((line) => !presentLines.has(line));\n\n if (missing.length === 0) {\n return {\n path: filePath,\n action: 'skip',\n description: existing === null ? 'no gitignore needed' : 'gitignore already covers Substrata',\n };\n }\n\n const header = '# Substrata generated files';\n const block = presentLines.has(header) ? missing.join('\\n') : `${header}\\n${missing.join('\\n')}`;\n\n let next: string;\n if (existing === null) {\n next = `${block}\\n`;\n } else {\n const sep = existing.endsWith('\\n') ? '' : '\\n';\n next = `${existing}${sep}${block}\\n`;\n }\n\n if (!dry) writeFileSync(filePath, next, 'utf8');\n\n return {\n path: filePath,\n action: existing === null ? 'create' : 'update',\n description: `add ${missing.length} gitignore line(s)`,\n contents: next,\n };\n}\n","import { lstatSync } from 'node:fs';\n\n/**\n * Setup writers refuse to write through symlinks: a repo could ship e.g. an\n * `AGENTS.md -> ~/.ssh/config` link and turn a marker-block upsert into a\n * clobber of a file outside the repo. Shell-rc writes are exempt by design —\n * dotfiles are commonly symlinked and the user explicitly opts into that write.\n */\nexport function isSymlink(filePath: string): boolean {\n try {\n return lstatSync(filePath).isSymbolicLink();\n } catch {\n return false;\n }\n}\n","import { readFileSync, writeFileSync } from 'node:fs';\n\nimport type { AttributionEnv, ChangeResult } from '../types';\n\n/**\n * Write a Substrata env block to a shell rc file, delimited by markers so a\n * re-run replaces it in place rather than appending. Pure + dry-runnable.\n */\n\nconst BEGIN = '# >>> substrata >>>';\nconst END = '# <<< substrata <<<';\n\nfunction readIfExists(filePath: string): string | null {\n try {\n return readFileSync(filePath, 'utf8');\n } catch {\n return null;\n }\n}\n\nfunction renderBlock(vars: AttributionEnv): string {\n const lines: string[] = [BEGIN, '# Substrata agent attribution (managed; safe to edit values)'];\n if (vars.actor) lines.push(`export SUBSTRATA_ACTOR=\"${vars.actor}\"`);\n if (vars.model) lines.push(`export SUBSTRATA_MODEL=\"${vars.model}\"`);\n if (vars.requester) lines.push(`export SUBSTRATA_REQUESTER=\"${vars.requester}\"`);\n lines.push(END);\n return lines.join('\\n');\n}\n\n/**\n * Insert or replace the Substrata env block in `rcPath`.\n * The block is delimited by stable markers; reruns replace in place.\n */\nexport function writeShellEnv(\n rcPath: string,\n vars: AttributionEnv,\n dry: boolean = false,\n): ChangeResult {\n const block = renderBlock(vars);\n const existing = readIfExists(rcPath);\n\n const markerRe = new RegExp(`${escapeRegExp(BEGIN)}[\\\\s\\\\S]*?${escapeRegExp(END)}`, 'm');\n\n let next: string;\n let action: ChangeResult['action'];\n\n if (existing === null) {\n next = `${block}\\n`;\n action = 'create';\n } else if (markerRe.test(existing)) {\n const replaced = existing.replace(markerRe, block);\n if (replaced === existing) {\n return { path: rcPath, action: 'skip', description: 'env block already current' };\n }\n next = replaced;\n action = 'update';\n } else {\n const sep = existing.endsWith('\\n') ? '' : '\\n';\n next = `${existing}${sep}\\n${block}\\n`;\n action = 'update';\n }\n\n if (!dry) writeFileSync(rcPath, next, 'utf8');\n\n return {\n path: rcPath,\n action,\n description: 'write Substrata env block',\n contents: next,\n };\n}\n\nfunction escapeRegExp(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n","import { readFileSync, writeFileSync } from 'node:fs';\nimport path from 'node:path';\n\nimport type { ChangeResult } from '../types';\nimport { isSymlink } from './symlink';\n\n/**\n * Insert/replace the Substrata section in AGENTS.md between begin/end markers.\n * Replace-in-place on rerun; never duplicates. Pure + dry-runnable. See plan §10.\n */\n\nconst BEGIN = '<!-- substrata:start -->';\nconst END = '<!-- substrata:end -->';\n\nexport const AGENTS_MD_SECTION = `${BEGIN}\n## Substrata Rules\n\nThis repository uses Substrata for shared agent memory.\n\nPrefer the MCP tools (\\`substrata_context\\`, \\`substrata_search\\`, \\`substrata_add\\`,\n\\`substrata_related_to_file\\`, \\`substrata_list_recent\\`) when they are available.\nFor shell usage, run the CLI as \\`npx -y substrata-cli <command>\\` — the bare\n\\`substrata\\` binary only exists if the package was installed globally.\n\nSet these once per agent session so footprints are attributed correctly:\n- \\`SUBSTRATA_ACTOR\\` (e.g. \"claude-code\")\n- \\`SUBSTRATA_MODEL\\` (e.g. \"claude-opus-4\")\n- \\`SUBSTRATA_REQUESTER\\` (falls back to git user.email)\n\nBefore making non-trivial changes:\n\n1. Get context with the MCP tool \\`substrata_context\\` or\n \\`npx -y substrata-cli context \"<task description>\"\\`.\n2. Search related decisions with \\`substrata_search\\` or \\`npx -y substrata-cli search\\`.\n3. Respect prior architectural decisions unless the user explicitly asks to override them.\n\nAfter making non-trivial changes:\n\n1. Add a footprint with the MCP tool \\`substrata_add\\` or \\`npx -y substrata-cli add\\`.\n2. Include: purpose, requester, actor, files changed, decisions made, rejected\n alternatives, implementation notes, commands run, memory learned, future agent guidance.\n3. If the work changes durable repo conventions, update \\`.substrata/memory/\\`.\n4. If the work reverses a prior decision, use \\`npx -y substrata-cli supersede\\`.\n\nDo not store secrets, credentials, private keys, tokens, or sensitive user data in Substrata files.\n${END}`;\n\nfunction readIfExists(filePath: string): string | null {\n try {\n return readFileSync(filePath, 'utf8');\n } catch {\n return null;\n }\n}\n\nfunction escapeRegExp(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n/** Upsert the Substrata section into `<cwd>/AGENTS.md`. */\nexport function upsertAgentsMd(cwd: string, dry: boolean = false): ChangeResult {\n const filePath = path.join(cwd, 'AGENTS.md');\n if (isSymlink(filePath)) {\n return { path: filePath, action: 'skip', description: 'refused: AGENTS.md is a symlink' };\n }\n const existing = readIfExists(filePath);\n const markerRe = new RegExp(`${escapeRegExp(BEGIN)}[\\\\s\\\\S]*?${escapeRegExp(END)}`, 'm');\n\n let next: string;\n let action: ChangeResult['action'];\n\n if (existing === null) {\n next = `${AGENTS_MD_SECTION}\\n`;\n action = 'create';\n } else if (markerRe.test(existing)) {\n const replaced = existing.replace(markerRe, AGENTS_MD_SECTION);\n if (replaced === existing) {\n return { path: filePath, action: 'skip', description: 'AGENTS.md section already current' };\n }\n next = replaced;\n action = 'update';\n } else {\n const sep = existing.endsWith('\\n') ? '' : '\\n';\n next = `${existing}${sep}\\n${AGENTS_MD_SECTION}\\n`;\n action = 'update';\n }\n\n if (!dry) writeFileSync(filePath, next, 'utf8');\n\n return {\n path: filePath,\n action,\n description: 'write Substrata AGENTS.md section',\n contents: next,\n };\n}\n","import { chmodSync, readFileSync, writeFileSync } from 'node:fs';\nimport path from 'node:path';\n\nimport type { ChangeResult } from '../types';\nimport { isSymlink } from './symlink';\n\n/**\n * Install a pre-commit hook that runs the Substrata staged-file secret scan as a\n * second line of defense. If a hook already exists, append a guarded block\n * rather than clobbering it. Pure-ish + dry-runnable. See plan §8.11/§12.\n */\n\nconst GUARD_BEGIN = '# >>> substrata pre-commit >>>';\nconst GUARD_END = '# <<< substrata pre-commit <<<';\n\nconst GUARDED_BLOCK = `${GUARD_BEGIN}\n# Substrata secret scan over staged .substrata files (second line of defense).\nnpx --no-install substrata internal-scan-staged || exit 1\n${GUARD_END}`;\n\nconst FULL_HOOK = `#!/bin/sh\n${GUARDED_BLOCK}\n`;\n\nfunction readIfExists(filePath: string): string | null {\n try {\n return readFileSync(filePath, 'utf8');\n } catch {\n return null;\n }\n}\n\n/** Install/refresh the pre-commit hook at `<cwd>/.git/hooks/pre-commit`. */\nexport function installSecretHook(cwd: string, dry: boolean = false): ChangeResult {\n const filePath = path.join(cwd, '.git', 'hooks', 'pre-commit');\n if (isSymlink(filePath)) {\n return { path: filePath, action: 'skip', description: 'refused: pre-commit hook is a symlink' };\n }\n const existing = readIfExists(filePath);\n\n let next: string;\n let action: ChangeResult['action'];\n\n if (existing === null) {\n next = FULL_HOOK;\n action = 'create';\n } else if (existing.includes(GUARD_BEGIN)) {\n return { path: filePath, action: 'skip', description: 'pre-commit hook already installed' };\n } else {\n const sep = existing.endsWith('\\n') ? '' : '\\n';\n next = `${existing}${sep}\\n${GUARDED_BLOCK}\\n`;\n action = 'update';\n }\n\n if (!dry) {\n writeFileSync(filePath, next, 'utf8');\n chmodSync(filePath, 0o755);\n }\n\n return {\n path: filePath,\n action,\n description: 'install Substrata pre-commit secret hook',\n contents: next,\n };\n}\n","import type { ChangeResult } from '../types';\n\n/**\n * Aggregate ChangeResults into a printable setup plan. Pure (no I/O).\n */\n\nconst ACTION_LABEL: Record<ChangeResult['action'], string> = {\n create: 'CREATE',\n update: 'UPDATE',\n skip: 'SKIP ',\n};\n\n/** Render a list of changes as aligned, human-readable plan lines. */\nexport function renderPlan(changes: ChangeResult[]): string {\n if (changes.length === 0) return 'No changes.';\n return changes\n .map((c) => `${ACTION_LABEL[c.action]} ${c.path}${c.description ? ` — ${c.description}` : ''}`)\n .join('\\n');\n}\n\n/** True if any change actually writes (create/update). */\nexport function hasEffectiveChanges(changes: ChangeResult[]): boolean {\n return changes.some((c) => c.action !== 'skip');\n}\n\n/** Summary counts by action, e.g. for a plan footer. */\nexport function summarizePlan(changes: ChangeResult[]): {\n create: number;\n update: number;\n skip: number;\n} {\n const summary = { create: 0, update: 0, skip: 0 };\n for (const c of changes) summary[c.action] += 1;\n return summary;\n}\n","import { stat } from 'node:fs/promises';\n\nimport {\n listFootprints,\n listMemoryDocuments,\n relativeToCwd,\n type Footprint,\n type MemoryDocument,\n} from '@substrata/core';\nimport type Database from 'better-sqlite3';\n\nimport { applySchema, dropSchema, SCHEMA_VERSION } from './schema';\nimport { closeDb, openIndexDb } from './sqlite';\n\nexport type BuildIndexOptions = {\n /**\n * Reserved for API symmetry with the plan signature. The MVP performs a full\n * rebuild on every call (drop & recreate rows), so this flag is a no-op today\n * but kept so callers can pass it without breaking.\n */\n rebuild?: boolean;\n};\n\n/** Row shape inserted into the `documents` table. */\ntype DocumentRow = {\n id: string;\n type: 'footprint' | 'memory';\n title: string;\n filePath: string;\n status: string | null;\n createdAt: string | null;\n updatedAt: string | null;\n tags: string[];\n files: string[];\n /** Full searchable content (joined sections / body). */\n content: string;\n workType: string | null;\n};\n\nfunction joinNonEmpty(parts: Array<string | undefined | null>): string {\n return parts\n .map((p) => (p ?? '').trim())\n .filter((p) => p.length > 0)\n .join('\\n\\n');\n}\n\n/** Flatten a footprint into a single searchable document row. */\nfunction footprintToRow(cwd: string, fp: Footprint): DocumentRow {\n const fm = fp.frontmatter;\n const s = fp.sections;\n\n const rejected = (s.rejectedOptions ?? []).map((r) => `${r.option}: ${r.reason}`).join('\\n');\n\n const content = joinNonEmpty([\n fp.title,\n s.purpose,\n (s.decisions ?? []).join('\\n'),\n rejected,\n s.implementationNotes,\n (s.memoryLearned ?? []).join('\\n'),\n s.futureAgentGuidance,\n (fm.tags ?? []).join(' '),\n (fm.files_touched ?? []).join(' '),\n ]);\n\n return {\n id: fm.id,\n type: 'footprint',\n title: fp.title,\n filePath: relativeToCwd(cwd, fp.filePath),\n status: fm.status,\n createdAt: fm.created_at,\n updatedAt: fm.updated_at ?? null,\n tags: fm.tags ?? [],\n files: fm.files_touched ?? [],\n content,\n workType: fm.work_type,\n };\n}\n\n/** Flatten a memory document into a single searchable document row. */\nfunction memoryToRow(cwd: string, doc: MemoryDocument): DocumentRow {\n const fm = doc.frontmatter;\n const tags = Array.isArray(fm.tags) ? fm.tags : [];\n\n const content = joinNonEmpty([doc.title, doc.body, tags.join(' ')]);\n\n return {\n id: fm.id,\n type: 'memory',\n title: doc.title,\n filePath: relativeToCwd(cwd, doc.filePath),\n status: null,\n createdAt: null,\n updatedAt: typeof fm.updated_at === 'string' ? fm.updated_at : null,\n tags,\n files: [],\n content,\n workType: null,\n };\n}\n\nasync function maxMtimeMs(filePaths: string[]): Promise<number> {\n let max = 0;\n for (const filePath of filePaths) {\n try {\n const st = await stat(filePath);\n if (st.mtimeMs > max) max = st.mtimeMs;\n } catch {\n // file vanished between listing and stat; ignore.\n }\n }\n return max;\n}\n\nfunction writeRows(db: Database.Database, rows: DocumentRow[]): void {\n const insertDoc = db.prepare(\n `INSERT INTO documents\n (id, type, title, file_path, status, created_at, updated_at, tags_json, files_json, raw_text, work_type)\n VALUES\n (@id, @type, @title, @filePath, @status, @createdAt, @updatedAt, @tagsJson, @filesJson, @content, @workType)`,\n );\n const insertFts = db.prepare(\n `INSERT INTO documents_fts (id, title, tags, files, content)\n VALUES (@id, @title, @tags, @files, @content)`,\n );\n\n for (const row of rows) {\n insertDoc.run({\n id: row.id,\n type: row.type,\n title: row.title,\n filePath: row.filePath,\n status: row.status,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n tagsJson: JSON.stringify(row.tags),\n filesJson: JSON.stringify(row.files),\n content: row.content,\n workType: row.workType,\n });\n insertFts.run({\n id: row.id,\n title: row.title,\n tags: row.tags.join(' '),\n files: row.files.join(' '),\n content: row.content,\n });\n }\n}\n\nfunction writeMeta(\n db: Database.Database,\n meta: { sourceMaxMtime: number; sourceFileCount: number },\n): void {\n const upsert = db.prepare(\n `INSERT INTO index_meta (key, value) VALUES (@key, @value)\n ON CONFLICT(key) DO UPDATE SET value = excluded.value`,\n );\n upsert.run({ key: 'schema_version', value: String(SCHEMA_VERSION) });\n upsert.run({ key: 'built_at', value: new Date().toISOString() });\n upsert.run({ key: 'source_max_mtime', value: String(meta.sourceMaxMtime) });\n upsert.run({ key: 'source_file_count', value: String(meta.sourceFileCount) });\n}\n\n/**\n * Build (or fully rebuild) the search index for `cwd`. Loads footprints and\n * memory documents via `@substrata/core`, flattens each into one searchable\n * `documents` + `documents_fts` row, and records freshness metadata. The MVP\n * always does a full rebuild (drop & recreate rows) inside a transaction.\n */\nexport async function buildIndex(cwd: string, _options: BuildIndexOptions = {}): Promise<void> {\n const [footprints, memory] = await Promise.all([listFootprints(cwd), listMemoryDocuments(cwd)]);\n\n const rows: DocumentRow[] = [\n ...footprints.map((fp) => footprintToRow(cwd, fp)),\n ...memory.map((doc) => memoryToRow(cwd, doc)),\n ];\n\n const sourceFiles = [\n ...footprints.map((fp) => fp.filePath),\n ...memory.map((doc) => doc.filePath),\n ];\n const sourceMaxMtime = await maxMtimeMs(sourceFiles);\n\n const db = openIndexDb(cwd);\n try {\n const rebuild = db.transaction(() => {\n // Full rebuild: drop & recreate so a schema bump or removed source files\n // never leave stale rows behind.\n dropSchema(db);\n applySchema(db);\n writeRows(db, rows);\n writeMeta(db, { sourceMaxMtime, sourceFileCount: sourceFiles.length });\n });\n rebuild();\n } finally {\n closeDb(db);\n }\n}\n","import type Database from 'better-sqlite3';\n\n/**\n * SQLite index schema. See plan §11 (\"SQLite schema\").\n *\n * `documents` holds the structured row (for filtering + ranking inputs);\n * `documents_fts` is the FTS5 virtual table queried via MATCH; `index_meta`\n * carries freshness metadata (schema_version, built_at, source_max_mtime,\n * source_file_count) so the index can be detected as missing/stale/fresh\n * without re-parsing source files.\n */\n\n/** Bump when the schema or indexing semantics change. */\nexport const SCHEMA_VERSION = 1;\n\nconst CREATE_DOCUMENTS = `\nCREATE TABLE IF NOT EXISTS documents (\n id TEXT PRIMARY KEY,\n type TEXT NOT NULL,\n title TEXT NOT NULL,\n file_path TEXT NOT NULL,\n status TEXT,\n created_at TEXT,\n updated_at TEXT,\n tags_json TEXT NOT NULL,\n files_json TEXT NOT NULL,\n raw_text TEXT NOT NULL,\n work_type TEXT\n);\n`;\n\nconst CREATE_DOCUMENTS_FTS = `\nCREATE VIRTUAL TABLE IF NOT EXISTS documents_fts USING fts5(\n id UNINDEXED,\n title,\n tags,\n files,\n content,\n tokenize = 'porter unicode61'\n);\n`;\n\nconst CREATE_INDEX_META = `\nCREATE TABLE IF NOT EXISTS index_meta (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL\n);\n`;\n\n/** Create all tables if they do not already exist. Idempotent. */\nexport function applySchema(db: Database.Database): void {\n db.exec(CREATE_DOCUMENTS);\n db.exec(CREATE_DOCUMENTS_FTS);\n db.exec(CREATE_INDEX_META);\n}\n\n/** Drop all index tables (used by a full rebuild). */\nexport function dropSchema(db: Database.Database): void {\n db.exec('DROP TABLE IF EXISTS documents;');\n db.exec('DROP TABLE IF EXISTS documents_fts;');\n db.exec('DROP TABLE IF EXISTS index_meta;');\n}\n","import { existsSync, mkdirSync } from 'node:fs';\nimport path from 'node:path';\n\nimport { indexPath } from '@substrata/core';\nimport Database from 'better-sqlite3';\n\nimport { applySchema } from './schema';\n\nexport type OpenIndexDbOptions = {\n /** Open the existing DB read-only; never create or migrate. */\n readonly?: boolean;\n};\n\n/**\n * Open (or create) the SQLite index database at `@substrata/core`'s\n * `indexPath(cwd)`. The parent `index/` directory is created if missing.\n *\n * In read-only mode the DB must already exist (better-sqlite3 will throw\n * otherwise); the schema is left untouched. In read-write mode the schema is\n * applied idempotently so a freshly created file is immediately usable.\n */\nexport function openIndexDb(cwd: string, options: OpenIndexDbOptions = {}): Database.Database {\n const dbPath = indexPath(cwd);\n\n if (!options.readonly) {\n mkdirSync(path.dirname(dbPath), { recursive: true });\n }\n\n const db = new Database(dbPath, { readonly: options.readonly ?? false });\n\n if (!options.readonly) {\n db.pragma('journal_mode = DELETE');\n applySchema(db);\n }\n\n return db;\n}\n\n/** True when an index database file exists on disk for `cwd`. */\nexport function indexDbExists(cwd: string): boolean {\n return existsSync(indexPath(cwd));\n}\n\n/** Close a database handle, ignoring errors from an already-closed handle. */\nexport function closeDb(db: Database.Database): void {\n try {\n db.close();\n } catch {\n // already closed; nothing to do.\n }\n}\n","import type { Dirent } from 'node:fs';\nimport { readdir, stat } from 'node:fs/promises';\nimport path from 'node:path';\n\nimport { footprintsDir, memoryDir, type IndexStatus } from '@substrata/core';\n\nimport { SCHEMA_VERSION } from './schema';\nimport { closeDb, indexDbExists, openIndexDb } from './sqlite';\n\n/**\n * Cheap stat walk of the footprint/memory dirs: collect the max mtime (ms) and\n * the count of `.md` files. No file contents are read or parsed — this mirrors\n * what `buildIndex` records so freshness can be checked without re-indexing.\n */\nasync function walkStats(dir: string): Promise<{ maxMtime: number; count: number }> {\n let entries: Dirent[];\n try {\n entries = await readdir(dir, { withFileTypes: true });\n } catch {\n return { maxMtime: 0, count: 0 };\n }\n\n let maxMtime = 0;\n let count = 0;\n for (const entry of entries) {\n const full = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n const sub = await walkStats(full);\n if (sub.maxMtime > maxMtime) maxMtime = sub.maxMtime;\n count += sub.count;\n } else if (entry.isFile() && entry.name.endsWith('.md')) {\n count += 1;\n try {\n const st = await stat(full);\n if (st.mtimeMs > maxMtime) maxMtime = st.mtimeMs;\n } catch {\n // ignore unreadable file\n }\n }\n }\n return { maxMtime, count };\n}\n\nfunction readMeta(cwd: string): Map<string, string> {\n const db = openIndexDb(cwd, { readonly: true });\n try {\n const rows = db.prepare('SELECT key, value FROM index_meta').all() as Array<{\n key: string;\n value: string;\n }>;\n return new Map(rows.map((r) => [r.key, r.value]));\n } finally {\n closeDb(db);\n }\n}\n\n/**\n * Report whether the on-disk index is missing, stale, or fresh for `cwd`.\n *\n * - missing: no index DB file on disk.\n * - stale/schema: recorded schema_version differs from the current one.\n * - stale/count: the source `.md` file count changed.\n * - stale/mtime: a source file is newer than the recorded build mtime.\n * - fresh: otherwise.\n *\n * Comparison is against a cheap stat walk only (plan §11) — no parsing.\n */\nexport async function getIndexStatus(cwd: string): Promise<IndexStatus> {\n if (!indexDbExists(cwd)) {\n return { state: 'missing' };\n }\n\n let meta: Map<string, string>;\n try {\n meta = readMeta(cwd);\n } catch {\n // Corrupt or unreadable DB — treat as missing so callers rebuild.\n return { state: 'missing' };\n }\n\n const recordedSchema = Number(meta.get('schema_version'));\n if (!Number.isFinite(recordedSchema) || recordedSchema !== SCHEMA_VERSION) {\n return { state: 'stale', reason: 'schema' };\n }\n\n const [fp, mem] = await Promise.all([walkStats(footprintsDir(cwd)), walkStats(memoryDir(cwd))]);\n const sourceCount = fp.count + mem.count;\n const sourceMaxMtime = Math.max(fp.maxMtime, mem.maxMtime);\n\n const recordedCount = Number(meta.get('source_file_count'));\n if (!Number.isFinite(recordedCount) || recordedCount !== sourceCount) {\n return { state: 'stale', reason: 'count' };\n }\n\n const recordedMtime = Number(meta.get('source_max_mtime'));\n // Allow a 1ms tolerance for filesystem mtime granularity differences.\n if (!Number.isFinite(recordedMtime) || sourceMaxMtime > recordedMtime + 1) {\n return { state: 'stale', reason: 'mtime' };\n }\n\n return { state: 'fresh' };\n}\n","import path from 'node:path';\n\nimport { toPosix, type FootprintStatus, type SearchResult, type WorkType } from '@substrata/core';\nimport type Database from 'better-sqlite3';\n\nimport { score } from './ranking';\nimport { closeDb, openIndexDb } from './sqlite';\n\nexport type SearchOptions = {\n cwd: string;\n limit?: number;\n /** Hard filter: only docs whose files_touched include one of these. */\n files?: string[];\n /** Hard filter: only docs carrying one of these tags. */\n tags?: string[];\n /** Drop superseded AND deprecated docs entirely. */\n excludeSuperseded?: boolean;\n};\n\nconst DEFAULT_LIMIT = 8;\n\n/**\n * Baseline relevance assigned to a direct file-hit in `getRelatedToFile` (these\n * rows do not come from an FTS MATCH, so they have no bm25). Chosen negative\n * (bm25 convention: lower is better) so a strong file match ranks comparably to\n * a good text match before the ×1.5 file-overlap boost is applied.\n */\nconst FILE_HIT_BM25 = -10;\n\n/** Row joined from documents + the FTS match. */\ntype JoinedRow = {\n id: string;\n type: string;\n title: string;\n file_path: string;\n status: string | null;\n created_at: string | null;\n updated_at: string | null;\n tags_json: string;\n files_json: string;\n work_type: string | null;\n bm25: number;\n snippet: string;\n};\n\nfunction parseJsonArray(value: string): string[] {\n try {\n const parsed = JSON.parse(value);\n return Array.isArray(parsed) ? (parsed as string[]) : [];\n } catch {\n return [];\n }\n}\n\n/**\n * Build a safe FTS5 MATCH expression from free-form user text. Each token is\n * wrapped in double quotes (with internal quotes doubled) so user punctuation\n * (e.g. `?!`, `:`, `-`) can never be interpreted as FTS5 query syntax. Tokens\n * are OR-ed so natural-language queries (e.g. \"why did we avoid Redis?!\") still\n * recall the relevant docs; ranking then orders by relevance. Returns null when\n * the query has no usable tokens.\n */\nexport function buildMatchQuery(query: string): string | null {\n const tokens = query\n .split(/[^A-Za-z0-9_]+/u)\n .map((t) => t.trim())\n .filter((t) => t.length > 0);\n if (tokens.length === 0) return null;\n return tokens.map((t) => `\"${t.replace(/\"/g, '\"\"')}\"`).join(' OR ');\n}\n\nfunction rowToResult(row: JoinedRow, queryFiles: string[]): SearchResult {\n const tags = parseJsonArray(row.tags_json);\n const files = parseJsonArray(row.files_json);\n const status = (row.status ?? 'completed') as FootprintStatus;\n\n const ranked = score(\n {\n bm25: row.bm25,\n status: row.status as FootprintStatus | null,\n workType: row.work_type as WorkType | null,\n updatedAt: row.updated_at,\n createdAt: row.created_at,\n docFiles: files,\n queryFiles,\n },\n Date.now(),\n );\n\n return {\n id: row.id,\n title: row.title,\n filePath: row.file_path,\n score: ranked,\n snippet: row.snippet,\n tags,\n createdAt: row.created_at ?? undefined,\n filesTouched: files,\n status,\n };\n}\n\ntype ExecuteOptions = {\n excludeSuperseded?: boolean;\n files?: string[];\n tags?: string[];\n limit: number;\n};\n\n/**\n * Run a MATCH query against an open DB, apply hard tag/file/status filters in\n * SQL, then rank + sort in JS. Shared by `search` and `getRelatedToFile`.\n */\nfunction executeMatch(\n db: Database.Database,\n match: string,\n queryFiles: string[],\n options: ExecuteOptions,\n): SearchResult[] {\n const where: string[] = [];\n const params: Record<string, unknown> = { match };\n\n if (options.excludeSuperseded) {\n // Memory docs have NULL status and are always kept; only footprint statuses\n // superseded/deprecated are dropped.\n where.push(\"(d.status IS NULL OR d.status NOT IN ('superseded', 'deprecated'))\");\n }\n\n if (options.tags && options.tags.length > 0) {\n const clauses = options.tags.map((tag, i) => {\n params[`tag${i}`] = tag;\n return `EXISTS (SELECT 1 FROM json_each(d.tags_json) WHERE json_each.value = @tag${i})`;\n });\n where.push(`(${clauses.join(' OR ')})`);\n }\n\n if (options.files && options.files.length > 0) {\n const clauses = options.files.map((file, i) => {\n params[`file${i}`] = toPosix(file);\n return `EXISTS (SELECT 1 FROM json_each(d.files_json) WHERE json_each.value = @file${i})`;\n });\n where.push(`(${clauses.join(' OR ')})`);\n }\n\n const whereSql = where.length > 0 ? ` AND ${where.join(' AND ')}` : '';\n\n const sql = `\n SELECT\n d.id, d.type, d.title, d.file_path, d.status,\n d.created_at, d.updated_at, d.tags_json, d.files_json, d.work_type,\n bm25(documents_fts) AS bm25,\n snippet(documents_fts, 4, '[', ']', ' … ', 12) AS snippet\n FROM documents_fts\n JOIN documents d ON d.id = documents_fts.id\n WHERE documents_fts MATCH @match${whereSql}\n `;\n\n const rows = db.prepare(sql).all(params) as JoinedRow[];\n const results = rows.map((row) => rowToResult(row, queryFiles));\n results.sort((a, b) => b.score - a.score);\n return results.slice(0, options.limit);\n}\n\n/**\n * Full-text search over footprints + memory. Builds a safe MATCH expression,\n * applies hard tag/file filters and optional superseded exclusion, ranks per\n * plan §11, and returns up to `limit` results.\n */\nexport async function search(query: string, options: SearchOptions): Promise<SearchResult[]> {\n const limit = options.limit ?? DEFAULT_LIMIT;\n const match = buildMatchQuery(query);\n if (!match) return [];\n\n const queryFiles = (options.files ?? []).map((f) => toPosix(f));\n\n const db = openIndexDb(options.cwd, { readonly: true });\n try {\n return executeMatch(db, match, queryFiles, {\n excludeSuperseded: options.excludeSuperseded,\n files: options.files,\n tags: options.tags,\n limit,\n });\n } finally {\n closeDb(db);\n }\n}\n\n/**\n * Find footprints/memory related to a specific file. Primary signal is a doc\n * whose `files_json` contains the posix-normalized path; an FTS fallback on the\n * filename stem broadens recall. Results are merged (dedup by id, file-hit\n * preferred) and ranked by the same rules.\n */\nexport async function getRelatedToFile(\n filePath: string,\n options: SearchOptions,\n): Promise<SearchResult[]> {\n const limit = options.limit ?? DEFAULT_LIMIT;\n const posixPath = toPosix(filePath);\n const stem = path.basename(posixPath).replace(/\\.[^.]+$/, '');\n\n const db = openIndexDb(options.cwd, { readonly: true });\n try {\n // Hard file matches: docs whose files_touched contain the exact path.\n const fileRows = db\n .prepare(\n `SELECT\n d.id, d.type, d.title, d.file_path, d.status,\n d.created_at, d.updated_at, d.tags_json, d.files_json, d.work_type\n FROM documents d\n WHERE EXISTS (\n SELECT 1 FROM json_each(d.files_json) WHERE json_each.value = @path\n )`,\n )\n .all({ path: posixPath }) as Omit<JoinedRow, 'bm25' | 'snippet'>[];\n\n const byId = new Map<string, SearchResult>();\n for (const row of fileRows) {\n const joined: JoinedRow = { ...row, bm25: FILE_HIT_BM25, snippet: '' };\n // The file hit counts as overlap automatically: queryFiles includes\n // posixPath, so rowToResult applies the ×1.5 boost via the ranker.\n const result = rowToResult(joined, [posixPath]);\n byId.set(result.id, result);\n }\n\n // FTS fallback on the filename stem to broaden recall.\n const match = buildMatchQuery(stem);\n if (match) {\n const ftsResults = executeMatch(db, match, [posixPath], {\n excludeSuperseded: options.excludeSuperseded,\n tags: options.tags,\n limit: limit * 4,\n });\n for (const r of ftsResults) {\n if (!byId.has(r.id)) byId.set(r.id, r);\n }\n }\n\n let merged = Array.from(byId.values());\n if (options.excludeSuperseded) {\n merged = merged.filter((r) => r.status !== 'superseded' && r.status !== 'deprecated');\n }\n merged.sort((a, b) => b.score - a.score);\n return merged.slice(0, limit);\n } finally {\n closeDb(db);\n }\n}\n","import type { FootprintStatus, WorkType } from '@substrata/core';\n\n/**\n * Ranking helpers. See plan §11 (\"Ranking (MVP)\").\n *\n * All helpers are pure so they can be unit-tested in isolation. `score` applies\n * the full pipeline: BM25 → normalize → multiplicative boosts → status penalty.\n */\n\n/** Days over which the recency boost decays linearly from 1 to 0. */\nexport const RECENCY_DECAY_DAYS = 180;\nconst MS_PER_DAY = 24 * 60 * 60 * 1000;\n\n/** Maximum fractional recency boost (plan: × (1 + 0.15 * decay)). */\nexport const RECENCY_BOOST_WEIGHT = 0.15;\n\n/** Multiplier when the doc's files overlap the queried files. */\nexport const FILES_OVERLAP_BOOST = 1.5;\n\n/** Multiplicative status penalties (plan §11 step 4). */\nexport const STATUS_PENALTIES: Record<FootprintStatus, number> = {\n draft: 0.5,\n completed: 1,\n superseded: 0.15,\n deprecated: 0.1,\n};\n\n/**\n * better-sqlite3's fts5 `bm25()` returns a score where *more negative is more\n * relevant*. Normalize to a positive relevance value so boosts/penalties read\n * naturally (higher = better).\n */\nexport function normalizeBm25(bm25: number): number {\n return -bm25;\n}\n\n/**\n * Linear recency decay in [0, 1]: 1 for \"now\", decaying to 0 at\n * RECENCY_DECAY_DAYS old and clamped at 0 beyond that. A missing/invalid date\n * yields 0 (no recency contribution).\n */\nexport function recencyDecay(timestamp: string | undefined, now: number = Date.now()): number {\n if (!timestamp) return 0;\n const t = Date.parse(timestamp);\n if (Number.isNaN(t)) return 0;\n const ageDays = (now - t) / MS_PER_DAY;\n if (ageDays <= 0) return 1;\n if (ageDays >= RECENCY_DECAY_DAYS) return 0;\n return 1 - ageDays / RECENCY_DECAY_DAYS;\n}\n\n/**\n * Recency boost multiplier: × (1 + weight * decay). For\n * `architecture_decision`, the boost contribution is halved so durable\n * decisions are not demoted merely for being old.\n */\nexport function recencyBoost(\n timestamp: string | undefined,\n workType: WorkType | null | undefined,\n now: number = Date.now(),\n): number {\n let contribution = RECENCY_BOOST_WEIGHT * recencyDecay(timestamp, now);\n if (workType === 'architecture_decision') {\n contribution /= 2;\n }\n return 1 + contribution;\n}\n\n/** True when any of the doc's files matches any queried file (case-insensitive). */\nexport function filesOverlap(docFiles: string[], queryFiles: string[]): boolean {\n if (queryFiles.length === 0 || docFiles.length === 0) return false;\n const want = new Set(queryFiles.map((f) => f.toLowerCase()));\n return docFiles.some((f) => want.has(f.toLowerCase()));\n}\n\n/** Multiplicative status penalty; unknown/undefined status is treated as neutral. */\nexport function statusPenalty(status: FootprintStatus | null | undefined): number {\n if (!status) return 1;\n return STATUS_PENALTIES[status] ?? 1;\n}\n\nexport type RankInput = {\n bm25: number;\n status?: FootprintStatus | null;\n workType?: WorkType | null;\n /** updated_at ?? created_at, used for recency. */\n updatedAt?: string | null;\n createdAt?: string | null;\n docFiles: string[];\n queryFiles: string[];\n};\n\n/**\n * Full ranking score: normalized BM25 with multiplicative file-overlap,\n * recency, and status modifiers applied per plan §11.\n */\nexport function score(input: RankInput, now: number = Date.now()): number {\n let s = normalizeBm25(input.bm25);\n\n if (filesOverlap(input.docFiles, input.queryFiles)) {\n s *= FILES_OVERLAP_BOOST;\n }\n\n const recencyTimestamp = input.updatedAt ?? input.createdAt ?? undefined;\n s *= recencyBoost(recencyTimestamp, input.workType ?? null, now);\n\n s *= statusPenalty(input.status ?? null);\n\n return s;\n}\n"],"mappings":"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","names":["path","parseYaml","stringifyYaml","readFile","readdir","writeFile","mkdir","readFileSync","writeFileSync","parseYaml","path","stringifyYaml","DEFAULT_REDACTION_KEYS","readFile","path","readFile","walkMarkdown","readdir","path","writeFile","FRONTMATTER_RE","parseYaml","stringifyYaml","stat","mkdir","readIfExists","readFileSync","writeFileSync","BEGIN","END","escapeRegExp","path","readdir","stat","path","path","path","readdir","stat"]}
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
search,
|
|
10
10
|
supersedeFootprint,
|
|
11
11
|
writeFootprint
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-XD2T4UBF.js";
|
|
13
13
|
|
|
14
14
|
// ../mcp-server/dist/index.js
|
|
15
15
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
@@ -290,4 +290,4 @@ export {
|
|
|
290
290
|
createSubstrataMcpServer,
|
|
291
291
|
runMcpServer
|
|
292
292
|
};
|
|
293
|
-
//# sourceMappingURL=dist-
|
|
293
|
+
//# sourceMappingURL=dist-NS4RCATY.js.map
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "substrata-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Substrata CLI — shared project memory for AI engineering agents",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -45,8 +45,8 @@
|
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@types/node": "^22.0.0",
|
|
48
|
-
"@substrata/mcp-server": "0.1.0",
|
|
49
48
|
"@substrata/core": "0.1.0",
|
|
49
|
+
"@substrata/mcp-server": "0.1.0",
|
|
50
50
|
"@substrata/search": "0.1.0"
|
|
51
51
|
},
|
|
52
52
|
"scripts": {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/wizard/prompts.ts","../src/util.ts","../src/commands/add.ts","../src/render/context.ts","../src/commands/auto-index.ts","../src/commands/context.ts","../src/commands/doctor.ts","../src/commands/hook.ts","../src/commands/index.ts","../src/commands/init.ts","../src/wizard/init-wizard.ts","../src/mcp-clients/claude-code.ts","../src/mcp-clients/json-config.ts","../src/mcp-clients/cursor.ts","../src/mcp-clients/windsurf.ts","../src/mcp-clients/registry.ts","../src/render/table.ts","../src/commands/list.ts","../src/commands/mcp.ts","../src/commands/memory-update.ts","../src/commands/search.ts","../src/commands/show.ts","../src/commands/supersede.ts"],"sourcesContent":["import { Command } from 'commander';\nimport pc from 'picocolors';\n\nimport { registerAddCommand } from './commands/add';\nimport { registerContextCommand } from './commands/context';\nimport { registerDoctorCommand } from './commands/doctor';\nimport { registerHookCommand } from './commands/hook';\nimport { registerIndexCommand } from './commands/index';\nimport { registerInitCommand } from './commands/init';\nimport { registerListCommand } from './commands/list';\nimport { registerMcpCommand } from './commands/mcp';\nimport { registerMemoryUpdateCommand } from './commands/memory-update';\nimport { registerSearchCommand } from './commands/search';\nimport { registerShowCommand } from './commands/show';\nimport { registerSupersedeCommand } from './commands/supersede';\nimport { CliError, out } from './util';\nimport { PromptCancelledError } from './wizard/prompts';\n\n/**\n * Build the commander program. Exported for tests: callers can do\n * `buildProgram().parseAsync(['node','substrata', ...])` and inject a cwd via the\n * hidden global `--cwd` option.\n *\n * Exit codes: 0 success, 1 user/expected errors (friendly, no stack), 2 unexpected.\n */\nexport function buildProgram(): Command {\n const program = new Command();\n\n program\n .name('substrata')\n .description('Shared project memory for AI engineering agents')\n .version('0.1.0')\n // Hidden global flag so tests can run commands against a temp dir.\n .option('--cwd <dir>', 'Run as if invoked from this directory')\n .enablePositionalOptions();\n\n // Don't let commander call process.exit() during tests/embedding.\n program.exitOverride();\n\n registerInitCommand(program);\n registerAddCommand(program);\n registerSearchCommand(program);\n registerContextCommand(program);\n registerIndexCommand(program);\n registerListCommand(program);\n registerShowCommand(program);\n registerDoctorCommand(program);\n registerSupersedeCommand(program);\n registerMemoryUpdateCommand(program);\n registerHookCommand(program);\n registerMcpCommand(program);\n\n return program;\n}\n\n/** Map an unknown error to an exit code, printing a friendly message. */\nfunction reportError(err: unknown): number {\n if (err instanceof CliError) {\n out.err(err.message);\n return err.exitCode;\n }\n if (err instanceof PromptCancelledError) {\n out.info('Cancelled.');\n return 1;\n }\n // Commander throws CommanderError for help/version/parse issues.\n const e = err as { code?: string; exitCode?: number; message?: string };\n if (e && typeof e.code === 'string' && e.code.startsWith('commander.')) {\n // help/version are not failures.\n if (e.code === 'commander.helpDisplayed' || e.code === 'commander.version') return 0;\n if (e.message) out.err(e.message);\n return typeof e.exitCode === 'number' ? e.exitCode : 1;\n }\n // Unexpected: surface the message without a stack, exit 2.\n out.err(`Unexpected error: ${(err as Error)?.message ?? String(err)}`);\n process.stderr.write(`${pc.dim('This is a bug — please file an issue.')}\\n`);\n return 2;\n}\n\n/** Parse argv and run. Returns the process exit code (does not call exit). */\nexport async function runCli(argv: string[]): Promise<number> {\n const program = buildProgram();\n try {\n await program.parseAsync(argv);\n return Number(process.exitCode ?? 0);\n } catch (err) {\n return reportError(err);\n }\n}\n","import {\n confirm as clackConfirm,\n isCancel,\n multiselect as clackMultiselect,\n text as clackText,\n type Option,\n} from '@clack/prompts';\n\n/**\n * Thin wrapper over @clack/prompts with a non-TTY guard.\n *\n * When stdout/stdin is not a TTY, or `assumeYes` is set, prompts must NOT render;\n * every prompt resolves to its supplied default. This keeps the wizard usable in\n * pipes/CI (which behave as `--yes`) and in tests without a fake terminal.\n */\n\nlet assumeYesFlag = false;\n\n/** Force non-interactive mode (used by `--yes` and non-TTY detection). */\nexport function setAssumeYes(value: boolean): void {\n assumeYesFlag = value;\n}\n\n/** True when prompts should be skipped (non-TTY or `--yes`). */\nexport function isNonInteractive(): boolean {\n return assumeYesFlag || !process.stdout.isTTY || !process.stdin.isTTY;\n}\n\n/** Raised when a user cancels an interactive prompt (Ctrl-C). */\nexport class PromptCancelledError extends Error {\n constructor() {\n super('Prompt cancelled');\n this.name = 'PromptCancelledError';\n }\n}\n\nfunction guardCancel<T>(value: T | symbol): T {\n if (isCancel(value)) throw new PromptCancelledError();\n return value as T;\n}\n\n/** Free-text prompt. Returns `defaultValue` in non-interactive mode. */\nexport async function promptText(options: {\n message: string;\n defaultValue: string;\n placeholder?: string;\n}): Promise<string> {\n if (isNonInteractive()) return options.defaultValue;\n const result = await clackText({\n message: options.message,\n placeholder: options.placeholder ?? options.defaultValue,\n defaultValue: options.defaultValue,\n initialValue: options.defaultValue,\n });\n const value = guardCancel(result);\n return value.length > 0 ? value : options.defaultValue;\n}\n\n/** Yes/no prompt. Returns `defaultValue` in non-interactive mode. */\nexport async function promptConfirm(options: {\n message: string;\n defaultValue: boolean;\n}): Promise<boolean> {\n if (isNonInteractive()) return options.defaultValue;\n const result = await clackConfirm({\n message: options.message,\n initialValue: options.defaultValue,\n });\n return guardCancel(result);\n}\n\n/** Multi-select prompt. Returns `defaultValues` in non-interactive mode. */\nexport async function promptMultiselect<Value>(options: {\n message: string;\n choices: Array<{ value: Value; label: string; hint?: string }>;\n defaultValues: Value[];\n}): Promise<Value[]> {\n if (isNonInteractive()) return options.defaultValues;\n if (options.choices.length === 0) return [];\n // `Option<Value>` is a conditional type; an unresolved generic can't be\n // simplified by TS, so build the array as the concrete shape and assert.\n const choices = options.choices.map((c) => ({\n value: c.value,\n label: c.label,\n ...(c.hint !== undefined ? { hint: c.hint } : {}),\n })) as Option<Value>[];\n\n const result = await clackMultiselect<Value>({\n message: options.message,\n options: choices,\n initialValues: options.defaultValues,\n required: false,\n });\n return guardCancel(result);\n}\n","import { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\n\nimport { loadConfig, type SubstrataConfig } from '@substrata/core';\nimport pc from 'picocolors';\n\n/**\n * Shared CLI helpers: cwd resolution, git shell-outs, attribution precedence,\n * and a typed error class for friendly (exit 1) failures.\n */\n\nconst execFileAsync = promisify(execFile);\n\n/** A user/expected error: printed as a friendly message, no stack, exit code 1. */\nexport class CliError extends Error {\n readonly exitCode: number;\n constructor(message: string, exitCode = 1) {\n super(message);\n this.name = 'CliError';\n this.exitCode = exitCode;\n }\n}\n\n/** Resolve the working directory from the hidden global `--cwd` option. */\nexport function resolveCwd(opts: { cwd?: string } | undefined): string {\n return opts?.cwd ? opts.cwd : process.cwd();\n}\n\nexport const out = {\n ok: (msg: string) => process.stdout.write(`${pc.green('✔')} ${msg}\\n`),\n info: (msg: string) => process.stdout.write(`${pc.blue('ℹ')} ${msg}\\n`),\n warn: (msg: string) => process.stderr.write(`${pc.yellow('!')} ${msg}\\n`),\n err: (msg: string) => process.stderr.write(`${pc.red('✖')} ${msg}\\n`),\n plain: (msg: string) => process.stdout.write(`${msg}\\n`),\n};\n\n/** Run a git command in `cwd`; returns trimmed stdout or null on failure. */\nexport async function git(cwd: string, args: string[]): Promise<string | null> {\n try {\n const { stdout } = await execFileAsync('git', args, { cwd });\n return stdout.trim();\n } catch {\n return null;\n }\n}\n\n/** True if `cwd` is inside a git work tree. */\nexport async function isGitRepo(cwd: string): Promise<boolean> {\n const result = await git(cwd, ['rev-parse', '--is-inside-work-tree']);\n return result === 'true';\n}\n\nexport type GitContext = {\n branch?: string;\n files: string[];\n commit?: string;\n};\n\n/** Collect branch, changed files (staged + unstaged), and HEAD commit. */\nexport async function collectGitContext(cwd: string): Promise<GitContext> {\n const branch = (await git(cwd, ['rev-parse', '--abbrev-ref', 'HEAD'])) ?? undefined;\n const commit = (await git(cwd, ['rev-parse', 'HEAD'])) ?? undefined;\n\n const staged = await git(cwd, ['diff', '--cached', '--name-only']);\n const unstaged = await git(cwd, ['diff', '--name-only']);\n const files = new Set<string>();\n for (const block of [staged, unstaged]) {\n if (!block) continue;\n for (const line of block.split(/\\r?\\n/)) {\n const t = line.trim();\n if (t.length > 0) files.add(t);\n }\n }\n\n return {\n branch: branch && branch !== 'HEAD' ? branch : undefined,\n files: Array.from(files),\n commit,\n };\n}\n\nexport type Attribution = {\n actor: string;\n model?: string;\n requester?: string;\n};\n\n/**\n * Resolve actor / model / requester by the precedence in plan §8.2.\n * actor: --actor → $SUBSTRATA_ACTOR → config.agent.default_actor → \"unknown-agent\"\n * model: --model → $SUBSTRATA_MODEL → config.agent.default_model → omit\n * requester: --requester → $SUBSTRATA_REQUESTER → git user.email → omit\n */\nexport async function resolveAttribution(\n cwd: string,\n config: SubstrataConfig,\n flags: { actor?: string; model?: string; requester?: string },\n): Promise<Attribution> {\n const env = process.env;\n\n const actor = flags.actor || env.SUBSTRATA_ACTOR || config.agent.default_actor || 'unknown-agent';\n\n const model = flags.model || env.SUBSTRATA_MODEL || config.agent.default_model || undefined;\n\n let requester = flags.requester || env.SUBSTRATA_REQUESTER || undefined;\n if (!requester) {\n const email = await git(cwd, ['config', 'user.email']);\n if (email) requester = email;\n }\n\n return { actor, model, requester };\n}\n\n/** Load config, mapping a missing/invalid config to a friendly CliError. */\nexport async function requireConfig(cwd: string): Promise<SubstrataConfig> {\n try {\n return await loadConfig(cwd);\n } catch (err) {\n throw new CliError(\n `${(err as Error).message}\\nRun \\`substrata init\\` to set up this repository.`,\n );\n }\n}\n","import {\n SecretDetectedError,\n supersedeFootprint,\n writeFootprint,\n type RejectedOption,\n type WorkType,\n type WriteFootprintInput,\n} from '@substrata/core';\nimport type { Command } from 'commander';\n\nimport { promptText, isNonInteractive } from '../wizard/prompts';\nimport {\n CliError,\n collectGitContext,\n out,\n requireConfig,\n resolveAttribution,\n resolveCwd,\n} from '../util';\n\n/**\n * `substrata add` — create a footprint, interactive or non-interactive. Resolves\n * actor/model/requester by precedence (plan §8.2), optionally enriches from Git\n * (`--from-git`), runs the secret scan inside core (catching SecretDetectedError\n * to print the §12 refusal), and prints the §12 commit reminder on success.\n */\n\nconst SECURITY_REMINDER =\n 'Reminder: Substrata files are intended to be committed.\\n' +\n 'Do not include secrets, credentials, or sensitive user data.';\n\ntype AddOptions = {\n title?: string;\n purpose?: string;\n actor?: string;\n requester?: string;\n model?: string;\n files?: string;\n tag?: string[];\n workType?: string;\n decision?: string[];\n rejected?: string[];\n notes?: string;\n memory?: string[];\n guidance?: string;\n template?: string;\n supersedes?: string;\n allowSecret?: boolean;\n fromGit?: boolean;\n};\n\nfunction collect(value: string, previous: string[] = []): string[] {\n return [...previous, value];\n}\n\nfunction splitCsv(value: string | undefined): string[] {\n if (!value) return [];\n return value\n .split(',')\n .map((s) => s.trim())\n .filter((s) => s.length > 0);\n}\n\n/** Parse a `--rejected \"option:reason\"` value into a structured RejectedOption. */\nfunction parseRejected(values: string[] | undefined): RejectedOption[] {\n if (!values) return [];\n return values.map((v) => {\n const idx = v.indexOf(':');\n if (idx === -1) return { option: v.trim(), reason: '' };\n return { option: v.slice(0, idx).trim(), reason: v.slice(idx + 1).trim() };\n });\n}\n\nconst VALID_WORK_TYPES: ReadonlySet<string> = new Set<WorkType>([\n 'implementation',\n 'implementation_decision',\n 'bug_fix',\n 'refactor',\n 'investigation',\n 'architecture_decision',\n 'test_update',\n 'documentation',\n]);\n\nexport function registerAddCommand(program: Command): void {\n program\n .command('add')\n .description('Create a new footprint')\n .option('--title <title>', 'Footprint title')\n .option('--purpose <text>', 'Why this work was done')\n .option('--actor <id>', 'Agent that performed the work')\n .option('--requester <id>', 'Who requested the work')\n .option('--model <id>', 'Agent model identifier')\n .option('--files <csv>', 'Comma-separated files touched')\n .option('--tag <tag>', 'Tag (repeatable)', collect, [])\n .option('--work-type <type>', 'Footprint work type')\n .option('--decision <text>', 'Decision made (repeatable)', collect, [])\n .option('--rejected <option:reason>', 'Rejected option (repeatable)', collect, [])\n .option('--notes <text>', 'Implementation notes')\n .option('--memory <text>', 'Memory learned (repeatable)', collect, [])\n .option('--guidance <text>', 'Future agent guidance')\n .option('--template <type>', 'Work type template to seed the footprint')\n .option('--supersedes <id>', 'Mark this footprint as superseding an old one')\n .option('--allow-secret', 'Write even if a secret is detected (NOT recommended)')\n .option('--from-git', 'Populate branch/files/commit from Git')\n .action(async (opts: AddOptions, command: Command) => {\n const cwd = resolveCwd(command.parent?.opts());\n const config = await requireConfig(cwd);\n\n const attribution = await resolveAttribution(cwd, config, {\n actor: opts.actor,\n model: opts.model,\n requester: opts.requester,\n });\n\n // Title: required in non-interactive mode; prompted otherwise.\n let title = opts.title;\n if (!title) {\n if (isNonInteractive()) {\n throw new CliError('`--title` is required in non-interactive mode.');\n }\n title = await promptText({ message: 'Title', defaultValue: '' });\n if (!title.trim()) throw new CliError('A title is required.');\n }\n\n const purpose =\n opts.purpose ??\n (isNonInteractive()\n ? undefined\n : (await promptText({ message: 'Purpose', defaultValue: '' })) || undefined);\n\n const workTypeRaw = opts.workType ?? opts.template;\n if (workTypeRaw && !VALID_WORK_TYPES.has(workTypeRaw)) {\n throw new CliError(\n `Invalid work type \"${workTypeRaw}\". Valid: ${Array.from(VALID_WORK_TYPES).join(', ')}.`,\n );\n }\n const workType = workTypeRaw as WorkType | undefined;\n\n let filesTouched = splitCsv(opts.files);\n let repoBranch: string | undefined;\n let commits: string[] | undefined;\n\n if (opts.fromGit) {\n const ctx = await collectGitContext(cwd);\n repoBranch = ctx.branch;\n if (ctx.files.length > 0) {\n filesTouched = Array.from(new Set([...filesTouched, ...ctx.files]));\n }\n if (ctx.commit) commits = [ctx.commit];\n }\n\n const supersedes = opts.supersedes ? [opts.supersedes] : undefined;\n\n const input: WriteFootprintInput & { cwd: string } = {\n cwd,\n title,\n purpose,\n actor: attribution.actor,\n requester: attribution.requester,\n agentModel: attribution.model,\n workType,\n decisions: opts.decision && opts.decision.length > 0 ? opts.decision : undefined,\n rejectedOptions:\n opts.rejected && opts.rejected.length > 0 ? parseRejected(opts.rejected) : undefined,\n implementationNotes: opts.notes,\n memoryLearned: opts.memory && opts.memory.length > 0 ? opts.memory : undefined,\n futureAgentGuidance: opts.guidance,\n filesTouched: filesTouched.length > 0 ? filesTouched : undefined,\n tags: opts.tag && opts.tag.length > 0 ? opts.tag : undefined,\n repo: repoBranch ? { branch: repoBranch } : undefined,\n related: commits ? { commits } : undefined,\n supersedes,\n allowSecret: opts.allowSecret,\n };\n\n let footprint;\n try {\n footprint = await writeFootprint(input);\n } catch (err) {\n if (err instanceof SecretDetectedError) {\n // §12 refusal: print pattern names + line numbers, NEVER values.\n const detail = err.findings.map((f) => ` - ${f.name} at body line ${f.line}`).join('\\n');\n throw new CliError(\n `Refusing to write footprint: ${err.findings.length} potential secret(s) detected\\n${detail}\\n` +\n ' Redact these or pass --allow-secret to override (NOT recommended — footprints are committed).',\n );\n }\n throw err;\n }\n\n // If superseding, also flip the old footprint's status/links.\n if (opts.supersedes) {\n await supersedeFootprint(cwd, opts.supersedes, footprint.frontmatter.id);\n }\n\n out.ok(`Footprint written: ${footprint.frontmatter.id}`);\n out.plain(` ${footprint.filePath}`);\n out.warn(SECURITY_REMINDER);\n });\n}\n","import type { Footprint, MemoryDocument, SearchResult } from '@substrata/core';\n\n/**\n * Build the LLM-friendly `context` output (plan §8.4). Each ranked source is\n * rendered as a numbered point with a one-line statement, an optional Reason,\n * and a Source: path. Sources are added in ranked order until adding the next\n * one would exceed the token budget.\n *\n * Token budget is a documented character approximation: `ceil(chars / 3.5)`.\n * No real tokenizer is bundled (plan §8.4). We round up so the estimate\n * under-fills rather than overflows.\n */\n\nconst CHARS_PER_TOKEN = 3.5;\n\n/** Approximate token count for a string (ceil(chars / 3.5)). */\nexport function estimateTokens(text: string): number {\n return Math.ceil(text.length / CHARS_PER_TOKEN);\n}\n\nexport type ContextSource = {\n id: string;\n title: string;\n filePath: string;\n};\n\nexport type ContextResult = {\n text: string;\n sources: ContextSource[];\n};\n\nconst HEADER = 'Relevant Substrata context:';\nconst APPROX_NOTE = '(Token counts are approximate — estimated, not tokenizer-exact.)';\n\n/** First non-empty line of a block of text, trimmed. */\nfunction firstLine(text: string | undefined): string | undefined {\n if (!text) return undefined;\n for (const line of text.split(/\\r?\\n/)) {\n const t = line.trim();\n if (t.length > 0) return t;\n }\n return undefined;\n}\n\n/**\n * Derive a concise statement + optional reason for a footprint, preferring the\n * most decision-bearing section available.\n */\nfunction footprintPoint(fp: Footprint): { statement: string; reason?: string } {\n const s = fp.sections;\n if (s.decisions && s.decisions.length > 0) {\n const rejected = s.rejectedOptions?.[0];\n return {\n statement: s.decisions[0]!,\n reason: rejected ? `${rejected.option} was rejected — ${rejected.reason}` : undefined,\n };\n }\n if (s.rejectedOptions && s.rejectedOptions.length > 0) {\n const r = s.rejectedOptions[0]!;\n return { statement: `Avoid ${r.option} for ${fp.title}.`, reason: r.reason };\n }\n if (s.futureAgentGuidance) {\n return { statement: firstLine(s.futureAgentGuidance) ?? fp.title };\n }\n if (s.purpose) {\n return { statement: fp.title, reason: firstLine(s.purpose) };\n }\n return { statement: fp.title };\n}\n\n/** Derive a concise statement for a memory document (first bullet or title). */\nfunction memoryPoint(doc: MemoryDocument): { statement: string } {\n for (const line of doc.body.split(/\\r?\\n/)) {\n const t = line.trim();\n if (t.startsWith('- ')) return { statement: t.slice(2).trim() };\n }\n return { statement: doc.title };\n}\n\ntype Resolved = { source: ContextSource; block: string };\n\nfunction blockFor(\n result: SearchResult,\n footprintsById: Map<string, Footprint>,\n memoryById: Map<string, MemoryDocument>,\n index: number,\n): Resolved {\n const fp = footprintsById.get(result.id);\n const mem = memoryById.get(result.id);\n\n let statement: string;\n let reason: string | undefined;\n if (fp) {\n ({ statement, reason } = footprintPoint(fp));\n } else if (mem) {\n ({ statement } = memoryPoint(mem));\n } else {\n statement = result.title || result.id;\n }\n\n const lines = [`${index}. ${statement}`];\n if (reason) lines.push(` Reason: ${reason}`);\n lines.push(` Source: ${result.filePath}`);\n\n return {\n source: { id: result.id, title: result.title, filePath: result.filePath },\n block: lines.join('\\n'),\n };\n}\n\n/**\n * Render ranked results into the LLM-friendly context string within a token\n * budget. Adds sources in order until the next would overflow `maxTokens`.\n */\nexport function renderContext(\n results: SearchResult[],\n footprints: Footprint[],\n memory: MemoryDocument[],\n maxTokens: number,\n): ContextResult {\n const footprintsById = new Map(footprints.map((f) => [f.frontmatter.id, f]));\n const memoryById = new Map(memory.map((m) => [m.frontmatter.id, m]));\n\n const header = `${HEADER}\\n\\n`;\n const footer = `\\n\\n${APPROX_NOTE}`;\n let used = estimateTokens(header) + estimateTokens(footer);\n\n const chosen: ContextSource[] = [];\n const blocks: string[] = [];\n\n let n = 1;\n for (const result of results) {\n const { source, block } = blockFor(result, footprintsById, memoryById, n);\n const blockTokens = estimateTokens(`${block}\\n\\n`);\n if (used + blockTokens > maxTokens && blocks.length > 0) break;\n blocks.push(block);\n chosen.push(source);\n used += blockTokens;\n n += 1;\n }\n\n if (blocks.length === 0) {\n return { text: `${HEADER}\\n\\nNo relevant context found.`, sources: [] };\n }\n\n return { text: `${header}${blocks.join('\\n\\n')}${footer}`, sources: chosen };\n}\n","import { buildIndex, getIndexStatus } from '@substrata/search';\n\nimport { out } from '../util';\n\n/**\n * Shared lazy-index helper for `search` and `context` (plan §8.3/§8.5). Rebuilds\n * a missing or stale index unless `--no-auto-index` was passed. A freshly cloned\n * repo (no index/) \"just works\" on the first query.\n */\nexport async function ensureFreshIndex(cwd: string, autoIndex: boolean): Promise<void> {\n const status = await getIndexStatus(cwd);\n if (status.state === 'fresh') return;\n\n if (!autoIndex) {\n out.info(\n status.state === 'missing'\n ? 'Index missing — run `substrata index` (auto-index disabled).'\n : `Index stale (${status.reason}) — run \\`substrata index\\` (auto-index disabled).`,\n );\n return;\n }\n\n await buildIndex(cwd);\n}\n","import { listFootprints, listMemoryDocuments } from '@substrata/core';\nimport { search } from '@substrata/search';\nimport type { Command } from 'commander';\n\nimport { renderContext } from '../render/context';\nimport { out, requireConfig, resolveCwd } from '../util';\n\nimport { ensureFreshIndex } from './auto-index';\n\n/**\n * `substrata context <task>` — concise, source-linked context for an agent\n * before work begins. Excludes superseded footprints by DEFAULT (agents should\n * see the current decision). Fits a token budget estimated as ceil(chars/3.5).\n * Auto-(re)builds a stale/missing index like `search` (plan §8.4).\n */\n\ntype ContextOptions = {\n json?: boolean;\n maxTokens?: string;\n files?: string[];\n autoIndex?: boolean;\n};\n\nfunction collect(value: string, previous: string[] = []): string[] {\n return [...previous, value];\n}\n\nexport function registerContextCommand(program: Command): void {\n program\n .command('context <task>')\n .description('Return concise, source-linked context for an agent')\n .option('--json', 'Output JSON ({ context, sources })')\n .option('--max-tokens <n>', 'Approximate token budget (chars/3.5)')\n .option('--files <path>', 'Bias toward docs touching this file (repeatable)', collect, [])\n .option('--no-auto-index', 'Do not auto-(re)build a stale/missing index')\n .action(async (task: string, opts: ContextOptions, command: Command) => {\n const cwd = resolveCwd(command.parent?.opts());\n const config = await requireConfig(cwd);\n\n await ensureFreshIndex(cwd, opts.autoIndex !== false);\n\n const maxTokens = opts.maxTokens ? Number(opts.maxTokens) : config.search.max_context_tokens;\n const budget = Number.isFinite(maxTokens) ? maxTokens : config.search.max_context_tokens;\n\n const results = await search(task, {\n cwd,\n limit: config.search.default_limit,\n files: opts.files && opts.files.length > 0 ? opts.files : undefined,\n excludeSuperseded: true,\n });\n\n const [footprints, memory] = await Promise.all([\n listFootprints(cwd),\n listMemoryDocuments(cwd),\n ]);\n\n const rendered = renderContext(results, footprints, memory, budget);\n\n if (opts.json) {\n out.plain(JSON.stringify({ context: rendered.text, sources: rendered.sources }, null, 2));\n return;\n }\n out.plain(rendered.text);\n });\n}\n","import { existsSync, readFileSync } from 'node:fs';\nimport path from 'node:path';\n\nimport {\n GITIGNORE_LINES,\n listFootprints,\n listMemoryDocuments,\n loadConfig,\n substrataDir,\n} from '@substrata/core';\nimport { getIndexStatus } from '@substrata/search';\nimport type { Command } from 'commander';\n\nimport { out, resolveCwd } from '../util';\n\n/**\n * `substrata doctor` — health check (plan §8.8). A missing/stale index is\n * informational (ℹ), not an error. Non-zero exit only for: invalid config,\n * unparseable footprint/memory files, or a gitignore that would commit the DB.\n *\n * Returns the number of hard failures so the program can set the exit code.\n */\nexport async function runDoctor(cwd: string): Promise<number> {\n let failures = 0;\n\n // .substrata exists\n if (existsSync(substrataDir(cwd))) {\n out.ok('.substrata exists');\n } else {\n out.err('.substrata missing — run `substrata init`');\n return 1;\n }\n\n // config valid\n try {\n await loadConfig(cwd);\n out.ok('config valid');\n } catch (err) {\n out.err(`config invalid: ${(err as Error).message}`);\n failures += 1;\n }\n\n // index status (informational only)\n const status = await getIndexStatus(cwd);\n if (status.state === 'fresh') {\n out.ok('index fresh');\n } else if (status.state === 'missing') {\n out.info('index missing — run `substrata index` (or it builds automatically on first search)');\n } else {\n out.info(`index stale (${status.reason}) — run \\`substrata index\\``);\n }\n\n // gitignore covers index/ and cache/ (else the generated DB would be committed)\n const gitignorePath = path.join(cwd, '.gitignore');\n const gitignore = existsSync(gitignorePath) ? readFileSync(gitignorePath, 'utf8') : '';\n const ignoredLines = new Set(gitignore.split(/\\r?\\n/).map((l) => l.trim()));\n const indexCovered = ignoredLines.has('.substrata/index/') || ignoredLines.has('.substrata/');\n if (indexCovered) {\n out.ok('gitignore covers index/ and cache/');\n } else {\n out.err(`gitignore would commit the generated DB — add: ${GITIGNORE_LINES.join(', ')}`);\n failures += 1;\n }\n\n // footprints parse\n try {\n const footprints = await listFootprints(cwd);\n out.ok(`${footprints.length} footprint file(s) parsed`);\n } catch (err) {\n out.err(`footprint parse error: ${(err as Error).message}`);\n failures += 1;\n }\n\n // memory parses\n try {\n const memory = await listMemoryDocuments(cwd);\n out.ok(`${memory.length} memory file(s) parsed`);\n } catch (err) {\n out.err(`memory parse error: ${(err as Error).message}`);\n failures += 1;\n }\n\n return failures;\n}\n\nexport function registerDoctorCommand(program: Command): void {\n program\n .command('doctor')\n .description('Check repository setup')\n .action(async (_opts: unknown, command: Command) => {\n const cwd = resolveCwd(command.parent?.opts());\n const failures = await runDoctor(cwd);\n if (failures > 0) {\n process.exitCode = 1;\n }\n });\n}\n","import { readFile } from 'node:fs/promises';\nimport path from 'node:path';\n\nimport { installSecretHook, scanForSecrets } from '@substrata/core';\nimport type { Command } from 'commander';\n\nimport { git, out, resolveCwd } from '../util';\n\n/**\n * `substrata hook install` — install the pre-commit secret hook (plan §8.11).\n * `substrata hook run` (and the internal `internal-scan-staged` alias the hook\n * script invokes) — scan staged `.substrata` files and exit 1 on findings.\n */\n\n/**\n * Scan staged `.substrata/**` files for secrets. Returns the number of files\n * with findings (0 = clean). Prints pattern names + line numbers, never values.\n */\nasync function scanStaged(cwd: string): Promise<number> {\n const staged = await git(cwd, ['diff', '--cached', '--name-only']);\n if (!staged) return 0;\n\n const files = staged\n .split(/\\r?\\n/)\n .map((l) => l.trim())\n .filter((l) => l.startsWith('.substrata/') && l.endsWith('.md'));\n\n let flagged = 0;\n for (const rel of files) {\n let content: string;\n try {\n content = await readFile(path.join(cwd, rel), 'utf8');\n } catch {\n continue;\n }\n const findings = scanForSecrets(content);\n if (findings.length > 0) {\n flagged += 1;\n out.err(`${findings.length} potential secret(s) in ${rel}:`);\n for (const f of findings) out.plain(` - ${f.name} at line ${f.line}`);\n }\n }\n return flagged;\n}\n\nexport function registerHookCommand(program: Command): void {\n const hook = program.command('hook').description('Pre-commit secret hook utilities');\n\n hook\n .command('install')\n .description('Install the pre-commit secret scan hook')\n .action(async (_opts: unknown, command: Command) => {\n const cwd = resolveCwd(command.parent?.parent?.opts());\n const result = installSecretHook(cwd);\n if (result.action === 'skip') {\n out.info('Pre-commit hook already installed.');\n } else {\n out.ok(`Pre-commit hook ${result.action === 'create' ? 'installed' : 'updated'}.`);\n }\n });\n\n hook\n .command('run')\n .description('Scan staged .substrata files for secrets')\n .action(async (_opts: unknown, command: Command) => {\n const cwd = resolveCwd(command.parent?.parent?.opts());\n const flagged = await scanStaged(cwd);\n if (flagged > 0) process.exitCode = 1;\n });\n\n // The installed hook script invokes this name directly.\n program\n .command('internal-scan-staged', { hidden: true })\n .action(async (_opts: unknown, command: Command) => {\n const cwd = resolveCwd(command.parent?.opts());\n const flagged = await scanStaged(cwd);\n if (flagged > 0) process.exitCode = 1;\n });\n}\n","import { buildIndex } from '@substrata/search';\nimport type { Command } from 'commander';\n\nimport { out, requireConfig, resolveCwd } from '../util';\n\n/**\n * `substrata index` / `index --rebuild` — build or rebuild the local FTS index.\n * The MVP indexer always does a full rebuild, so `--rebuild` is accepted for\n * symmetry with the documented flag.\n */\nexport function registerIndexCommand(program: Command): void {\n program\n .command('index')\n .description('Build or rebuild the local search index')\n .option('--rebuild', 'Force a full rebuild of the index')\n .action(async (_opts: { rebuild?: boolean }, command: Command) => {\n const cwd = resolveCwd(command.parent?.opts());\n await requireConfig(cwd);\n await buildIndex(cwd, { rebuild: true });\n out.ok('Index built.');\n });\n}\n","import { readFile, writeFile } from 'node:fs/promises';\n\nimport { configPath } from '@substrata/core';\nimport { buildIndex } from '@substrata/search';\nimport type { Command } from 'commander';\n\nimport { printResolvedConfig, runInitWizard, type InitFlags } from '../wizard/init-wizard';\nimport { setAssumeYes } from '../wizard/prompts';\nimport { out, resolveCwd } from '../util';\n\nimport { runDoctor } from './doctor';\n\n/**\n * `substrata init` — one-command setup wizard (plan §8.1). Non-TTY/`--yes`\n * accepts all defaults. After applying, runs the embedded doctor health check and\n * builds the initial index (unless `--no-index`).\n */\n\n/** Disable redaction in the written config when `--no-redact` was passed. */\nasync function disableRedaction(cwd: string): Promise<void> {\n const file = configPath(cwd);\n let raw: string;\n try {\n raw = await readFile(file, 'utf8');\n } catch {\n return;\n }\n const next = raw\n .replace(/^(\\s*)redact:\\s*true\\s*$/m, '$1redact: false')\n .replace(/^(\\s*)scan_content:\\s*true\\s*$/m, '$1scan_content: false')\n .replace(/^(\\s*)block_on_secret:\\s*true\\s*$/m, '$1block_on_secret: false');\n if (next !== raw) await writeFile(file, next, 'utf8');\n}\n\nexport function registerInitCommand(program: Command): void {\n program\n .command('init')\n .description('Set up Substrata in this repository (interactive wizard)')\n .option('--yes', 'Accept all defaults, no prompts')\n .option('--project <name>', 'Project name')\n .option('--actor <id>', 'Default actor')\n .option('--model <id>', 'Default agent model')\n .option('--requester <id>', 'Default requester')\n .option('--no-env', \"Don't touch shell rc; print snippet instead\")\n .option('--no-agents-md', 'Skip AGENTS.md')\n .option('--no-mcp', 'Skip MCP registration')\n .option('--mcp-client <name>', 'Register only this client (repeatable)', collect, [])\n .option('--no-gitignore', \"Don't edit .gitignore\")\n .option('--no-redact', 'Disable security redaction (NOT recommended)')\n .option('--with-hook', 'Install the pre-commit secret hook')\n .option('--no-index', 'Skip building the initial index')\n .option('--print-config', 'Print resolved config.yml without writing anything')\n .action(async (opts: InitCommandOptions, command: Command) => {\n const cwd = resolveCwd(command.parent?.opts());\n\n const flags: InitFlags = {\n yes: opts.yes,\n project: opts.project,\n actor: opts.actor,\n model: opts.model,\n requester: opts.requester,\n env: opts.env,\n agentsMd: opts.agentsMd,\n mcp: opts.mcp,\n mcpClient: opts.mcpClient,\n gitignore: opts.gitignore,\n redact: opts.redact,\n withHook: opts.withHook,\n index: opts.index,\n printConfig: opts.printConfig,\n };\n\n if (flags.printConfig) {\n printResolvedConfig(cwd, flags);\n return;\n }\n\n // Non-TTY contexts behave as --yes (handled inside the prompt wrapper too).\n if (flags.yes) setAssumeYes(true);\n\n const result = await runInitWizard(cwd, flags);\n if (result.aborted) return;\n\n if (flags.redact === false) {\n await disableRedaction(cwd);\n out.warn('Security redaction disabled (--no-redact).');\n }\n\n // Embedded health check (doctor logic).\n out.plain('');\n await runDoctor(cwd);\n\n // Build the initial index unless skipped.\n if (flags.index !== false) {\n await buildIndex(cwd);\n out.ok('Initial index built.');\n }\n });\n}\n\ntype InitCommandOptions = {\n yes?: boolean;\n project?: string;\n actor?: string;\n model?: string;\n requester?: string;\n env?: boolean;\n agentsMd?: boolean;\n mcp?: boolean;\n mcpClient?: string[];\n gitignore?: boolean;\n redact?: boolean;\n withHook?: boolean;\n index?: boolean;\n printConfig?: boolean;\n};\n\nfunction collect(value: string, previous: string[] = []): string[] {\n return [...previous, value];\n}\n","import { existsSync, readFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport path from 'node:path';\n\nimport {\n ensureGitignore,\n initProject,\n installSecretHook,\n renderConfig,\n upsertAgentsMd,\n writeShellEnv,\n type AttributionEnv,\n type ChangeResult,\n} from '@substrata/core';\nimport pc from 'picocolors';\n\nimport {\n detectMcpClients,\n getMcpClient,\n SUBSTRATA_MCP_SPEC,\n type McpClient,\n} from '../mcp-clients/registry';\nimport { git, isGitRepo, out } from '../util';\n\nimport { promptConfirm, promptMultiselect, promptText } from './prompts';\n\n/**\n * The `init` setup wizard (plan §8.1). Collects answers, prints a change plan,\n * and applies nothing until one final confirmation. Idempotent: re-running enters\n * update mode (markers replaced in place; gitignore/MCP de-duplicated).\n */\n\nexport type InitFlags = {\n yes?: boolean;\n project?: string;\n actor?: string;\n model?: string;\n requester?: string;\n env?: boolean; // --no-env => false\n agentsMd?: boolean; // --no-agents-md => false\n mcp?: boolean; // --no-mcp => false\n mcpClient?: string[];\n gitignore?: boolean; // --no-gitignore => false\n redact?: boolean; // --no-redact => false\n withHook?: boolean;\n index?: boolean; // --no-index => false\n printConfig?: boolean;\n};\n\ntype Answers = {\n projectName: string;\n attribution: AttributionEnv;\n writeEnv: boolean;\n rcPath: string;\n redact: boolean;\n withHook: boolean;\n writeAgentsMd: boolean;\n writeGitignore: boolean;\n mcpClients: McpClient[];\n};\n\n/** Detect the user's shell rc file from $SHELL, defaulting to ~/.zshrc. */\nfunction detectShellRc(): string {\n const shell = process.env.SHELL ?? '';\n const home = homedir();\n if (shell.includes('bash')) return path.join(home, '.bashrc');\n return path.join(home, '.zshrc');\n}\n\n/** Default project name: package.json \"name\", else the folder basename. */\nfunction defaultProjectName(cwd: string): string {\n const pkgPath = path.join(cwd, 'package.json');\n if (existsSync(pkgPath)) {\n try {\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf8')) as { name?: string };\n if (pkg.name && typeof pkg.name === 'string') {\n return pkg.name.replace(/^@[^/]+\\//, '');\n }\n } catch {\n // ignore malformed package.json\n }\n }\n return path.basename(path.resolve(cwd));\n}\n\n/** Step 0: preflight — git repo check (offer git init), MCP client detection. */\nasync function preflight(cwd: string, flags: InitFlags): Promise<void> {\n if (!(await isGitRepo(cwd))) {\n const doInit = await promptConfirm({\n message: 'This is not a Git repository. Run `git init`?',\n defaultValue: true,\n });\n if (doInit) {\n const result = await git(cwd, ['init']);\n if (result !== null) out.ok('Initialized a Git repository.');\n else out.warn('Could not run `git init`; continuing without Git.');\n }\n }\n void flags;\n}\n\n/** Resolve the set of MCP clients to register, honoring --mcp-client / --no-mcp. */\nasync function resolveMcpClients(cwd: string, flags: InitFlags): Promise<McpClient[]> {\n if (flags.mcp === false) return [];\n\n if (flags.mcpClient && flags.mcpClient.length > 0) {\n const chosen: McpClient[] = [];\n for (const name of flags.mcpClient) {\n const client = getMcpClient(name);\n if (client) chosen.push(client);\n else out.warn(`Unknown MCP client \"${name}\" — skipping.`);\n }\n return chosen;\n }\n\n const detected = await detectMcpClients(cwd);\n if (detected.length === 0) return [];\n\n return promptMultiselect<McpClient>({\n message: 'Register the Substrata MCP server with which clients?',\n choices: detected.map((c) => ({ value: c, label: c.label })),\n defaultValues: detected,\n });\n}\n\n/** Collect all wizard answers (no writes). */\nasync function collectAnswers(cwd: string, flags: InitFlags): Promise<Answers> {\n const projectName =\n flags.project ??\n (await promptText({\n message: 'Project name',\n defaultValue: defaultProjectName(cwd),\n }));\n\n // Agent attribution.\n const actor = await promptText({\n message: 'Default actor (agent id)',\n defaultValue: flags.actor ?? 'unknown-agent',\n });\n const model =\n flags.model ??\n (await promptText({ message: 'Default agent model (optional)', defaultValue: '' }));\n const gitEmail = (await git(cwd, ['config', 'user.email'])) ?? '';\n const requester =\n flags.requester ??\n (await promptText({ message: 'Default requester (optional)', defaultValue: gitEmail }));\n\n const attribution: AttributionEnv = {\n actor: actor || undefined,\n model: model || undefined,\n requester: requester || undefined,\n };\n\n const writeEnv =\n flags.env === false\n ? false\n : await promptConfirm({\n message: 'Persist attribution env vars to your shell rc?',\n defaultValue: true,\n });\n\n const redact =\n flags.redact === false\n ? false\n : await promptConfirm({\n message: 'Enable security redaction + content scan?',\n defaultValue: true,\n });\n\n const withHook =\n flags.withHook === true\n ? true\n : await promptConfirm({\n message: 'Install the pre-commit secret hook?',\n defaultValue: false,\n });\n\n const writeAgentsMd =\n flags.agentsMd === false\n ? false\n : await promptConfirm({\n message: 'Add the Substrata section to AGENTS.md?',\n defaultValue: true,\n });\n\n const writeGitignore = flags.gitignore !== false;\n\n const mcpClients = await resolveMcpClients(cwd, flags);\n\n return {\n projectName,\n attribution,\n writeEnv,\n rcPath: detectShellRc(),\n redact,\n withHook,\n writeAgentsMd,\n writeGitignore,\n mcpClients,\n };\n}\n\n/** Build the full change plan as dry-run ChangeResults (no writes). */\nasync function buildPlan(cwd: string, answers: Answers): Promise<ChangeResult[]> {\n const changes: ChangeResult[] = [];\n\n // Scaffold: simulate by checking what initProject would create.\n // initProject itself is idempotent (skips existing), but it writes; for the\n // plan we describe the scaffold as a single logical step.\n changes.push({\n path: path.join(cwd, '.substrata'),\n action: existsSync(path.join(cwd, '.substrata', 'config.yml')) ? 'skip' : 'create',\n description: 'Substrata scaffold (config, README, footprints, memory, templates)',\n });\n\n if (answers.writeGitignore) {\n changes.push(ensureGitignore(cwd, true));\n }\n\n if (\n answers.writeEnv &&\n (answers.attribution.actor || answers.attribution.model || answers.attribution.requester)\n ) {\n changes.push(writeShellEnv(answers.rcPath, answers.attribution, true));\n }\n\n if (answers.writeAgentsMd) {\n changes.push(upsertAgentsMd(cwd, true));\n }\n\n if (answers.withHook) {\n changes.push(installSecretHook(cwd, true));\n }\n\n for (const client of answers.mcpClients) {\n changes.push(await client.register(cwd, SUBSTRATA_MCP_SPEC, true));\n }\n\n return changes;\n}\n\n/** Apply the plan for real (idempotent writers). Returns applied changes. */\nasync function applyPlan(cwd: string, answers: Answers): Promise<ChangeResult[]> {\n const applied: ChangeResult[] = [];\n\n applied.push(...(await initProject(cwd, { projectName: answers.projectName })));\n\n if (answers.writeGitignore) {\n applied.push(ensureGitignore(cwd, false));\n }\n\n if (\n answers.writeEnv &&\n (answers.attribution.actor || answers.attribution.model || answers.attribution.requester)\n ) {\n applied.push(writeShellEnv(answers.rcPath, answers.attribution, false));\n } else if (!answers.writeEnv) {\n printEnvSnippet(answers.attribution);\n }\n\n if (answers.writeAgentsMd) {\n applied.push(upsertAgentsMd(cwd, false));\n }\n\n if (answers.withHook) {\n applied.push(installSecretHook(cwd, false));\n }\n\n for (const client of answers.mcpClients) {\n const result = await client.register(cwd, SUBSTRATA_MCP_SPEC, false);\n applied.push(result);\n // Windsurf etc. return a skip with a printable snippet in the description.\n if (result.action === 'skip' && result.description.includes('\\n')) {\n out.info(`${client.label}: ${result.description}`);\n }\n }\n\n return applied;\n}\n\n/** Print the export snippet when env persistence is declined (plan §8.1 step 2). */\nfunction printEnvSnippet(attribution: AttributionEnv): void {\n const lines: string[] = [];\n if (attribution.actor) lines.push(`export SUBSTRATA_ACTOR=\"${attribution.actor}\"`);\n if (attribution.model) lines.push(`export SUBSTRATA_MODEL=\"${attribution.model}\"`);\n if (attribution.requester) lines.push(`export SUBSTRATA_REQUESTER=\"${attribution.requester}\"`);\n if (lines.length === 0) return;\n out.info('Add these to your shell rc to attribute footprints:');\n out.plain(lines.map((l) => ` ${l}`).join('\\n'));\n}\n\n/** Render the resolved config.yml without writing (for --print-config). */\nexport function printResolvedConfig(cwd: string, flags: InitFlags): void {\n const name = flags.project ?? defaultProjectName(cwd);\n out.plain(renderConfig(name));\n}\n\nexport type WizardResult = {\n applied: ChangeResult[];\n aborted: boolean;\n};\n\n/** Run the full init wizard. Returns whether the user aborted. */\nexport async function runInitWizard(cwd: string, flags: InitFlags): Promise<WizardResult> {\n const updateMode = existsSync(path.join(cwd, '.substrata', 'config.yml'));\n if (updateMode) {\n out.info('Existing .substrata detected — running in update mode.');\n }\n\n await preflight(cwd, flags);\n const answers = await collectAnswers(cwd, flags);\n\n const plan = await buildPlan(cwd, answers);\n out.plain(pc.bold('\\nPlanned changes:'));\n out.plain(\n plan\n .map((c) => {\n const label =\n c.action === 'create'\n ? pc.green('CREATE')\n : c.action === 'update'\n ? pc.yellow('UPDATE')\n : pc.dim('SKIP ');\n return ` ${label} ${c.path}${c.description ? pc.dim(` — ${c.description.split('\\n')[0]}`) : ''}`;\n })\n .join('\\n'),\n );\n out.plain('');\n\n const confirmed = await promptConfirm({ message: 'Apply these changes?', defaultValue: true });\n if (!confirmed) {\n out.info('Aborted. Nothing was written.');\n return { applied: [], aborted: true };\n }\n\n const applied = await applyPlan(cwd, answers);\n out.ok('Substrata setup applied.');\n\n if (answers.writeEnv) {\n out.info(`Run \\`source ${answers.rcPath}\\` to load attribution env vars.`);\n }\n\n return { applied, aborted: false };\n}\n","import { execFile } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport path from 'node:path';\nimport { promisify } from 'node:util';\n\nimport type { ChangeResult } from '@substrata/core';\n\nimport { mergeMcpJson, removeMcpJson } from './json-config';\nimport type { McpClient, McpServerSpec } from './registry';\n\n/**\n * Claude Code MCP client. Detected via the `claude` binary on PATH OR an existing\n * project `.mcp.json`. Registration writes `.mcp.json` directly (rather than\n * shelling to `claude mcp add`) so it is deterministic and testable.\n */\n\nconst execFileAsync = promisify(execFile);\n\nfunction mcpJsonPath(cwd: string): string {\n return path.join(cwd, '.mcp.json');\n}\n\nasync function hasClaudeBinary(): Promise<boolean> {\n try {\n await execFileAsync('which', ['claude']);\n return true;\n } catch {\n return false;\n }\n}\n\nexport const claudeCodeClient: McpClient = {\n name: 'claude',\n label: 'Claude Code',\n\n async detect(cwd: string): Promise<boolean> {\n if (existsSync(mcpJsonPath(cwd))) return true;\n return hasClaudeBinary();\n },\n\n async register(cwd: string, spec: McpServerSpec, dry: boolean = false): Promise<ChangeResult> {\n return mergeMcpJson(mcpJsonPath(cwd), spec, dry);\n },\n\n async unregister(cwd: string, name: string): Promise<void> {\n removeMcpJson(mcpJsonPath(cwd), name);\n },\n};\n","import { lstatSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';\nimport path from 'node:path';\n\nimport type { ChangeResult } from '@substrata/core';\n\nimport type { McpServerSpec } from './registry';\n\n/**\n * Shared helper for clients that store MCP servers in a JSON file under an\n * `mcpServers` map (Claude Code `.mcp.json`, Cursor `.cursor/mcp.json`). The\n * write is idempotent: remove-then-add the named entry, preserving other keys.\n */\n\ntype McpJson = {\n mcpServers?: Record<string, { command: string; args?: string[] }>;\n [key: string]: unknown;\n};\n\nfunction isSymlink(filePath: string): boolean {\n try {\n return lstatSync(filePath).isSymbolicLink();\n } catch {\n return false;\n }\n}\n\nfunction readJson(filePath: string): McpJson | null {\n try {\n const raw = readFileSync(filePath, 'utf8');\n const parsed = JSON.parse(raw) as unknown;\n return parsed && typeof parsed === 'object' ? (parsed as McpJson) : {};\n } catch {\n return null;\n }\n}\n\n/** Whether the existing config already contains the spec entry exactly. */\nfunction entryMatches(existing: McpJson | null, spec: McpServerSpec): boolean {\n const entry = existing?.mcpServers?.[spec.name];\n if (!entry) return false;\n return (\n entry.command === spec.command && JSON.stringify(entry.args ?? []) === JSON.stringify(spec.args)\n );\n}\n\n/**\n * Merge the Substrata MCP server into a JSON config file at `filePath`.\n * Remove-then-add semantics keep the result idempotent across reruns.\n */\nexport function mergeMcpJson(\n filePath: string,\n spec: McpServerSpec,\n dry: boolean = false,\n): ChangeResult {\n // Refuse to write through a symlink: a repo-shipped link could redirect the\n // merge into a file outside the repo.\n if (isSymlink(filePath)) {\n return {\n path: filePath,\n action: 'skip',\n description: `refused: ${path.basename(filePath)} is a symlink`,\n };\n }\n const existing = readJson(filePath);\n\n if (entryMatches(existing, spec)) {\n return { path: filePath, action: 'skip', description: 'MCP entry already current' };\n }\n\n const base: McpJson = existing ?? {};\n const servers = { ...(base.mcpServers ?? {}) };\n // Remove-then-add so a drifted entry is replaced wholesale.\n delete servers[spec.name];\n servers[spec.name] = { command: spec.command, args: spec.args };\n\n const next: McpJson = { ...base, mcpServers: servers };\n const contents = `${JSON.stringify(next, null, 2)}\\n`;\n\n if (!dry) {\n mkdirSync(path.dirname(filePath), { recursive: true });\n writeFileSync(filePath, contents, 'utf8');\n }\n\n return {\n path: filePath,\n action: existing === null ? 'create' : 'update',\n description: `register Substrata MCP server (${spec.name})`,\n contents,\n };\n}\n\n/** Remove the named MCP entry from a JSON config file (idempotent). */\nexport function removeMcpJson(filePath: string, name: string): void {\n const existing = readJson(filePath);\n if (!existing?.mcpServers?.[name]) return;\n const servers = { ...existing.mcpServers };\n delete servers[name];\n const next: McpJson = { ...existing, mcpServers: servers };\n writeFileSync(filePath, `${JSON.stringify(next, null, 2)}\\n`, 'utf8');\n}\n","import { existsSync } from 'node:fs';\nimport path from 'node:path';\n\nimport type { ChangeResult } from '@substrata/core';\n\nimport { mergeMcpJson, removeMcpJson } from './json-config';\nimport type { McpClient, McpServerSpec } from './registry';\n\n/**\n * Cursor MCP client. Stores project MCP servers in `.cursor/mcp.json` using the\n * same `mcpServers` map shape as Claude Code. Detected via an existing\n * `.cursor/` directory or `.cursor/mcp.json`.\n */\n\nfunction cursorMcpPath(cwd: string): string {\n return path.join(cwd, '.cursor', 'mcp.json');\n}\n\nexport const cursorClient: McpClient = {\n name: 'cursor',\n label: 'Cursor',\n\n async detect(cwd: string): Promise<boolean> {\n return existsSync(path.join(cwd, '.cursor')) || existsSync(cursorMcpPath(cwd));\n },\n\n async register(cwd: string, spec: McpServerSpec, dry: boolean = false): Promise<ChangeResult> {\n return mergeMcpJson(cursorMcpPath(cwd), spec, dry);\n },\n\n async unregister(cwd: string, name: string): Promise<void> {\n removeMcpJson(cursorMcpPath(cwd), name);\n },\n};\n","import { existsSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport path from 'node:path';\n\nimport type { ChangeResult } from '@substrata/core';\n\nimport type { McpClient, McpServerSpec } from './registry';\n\n/**\n * Windsurf MCP client. Windsurf's MCP config is global per-user\n * (`~/.codeium/windsurf/mcp_config.json`), so registration does NOT edit the\n * user's homedir. Instead it returns a `skip` ChangeResult whose description is a\n * printable snippet + path the user can apply manually.\n */\n\nfunction windsurfConfigPath(): string {\n return path.join(homedir(), '.codeium', 'windsurf', 'mcp_config.json');\n}\n\nfunction snippet(spec: McpServerSpec): string {\n const block = {\n mcpServers: {\n [spec.name]: { command: spec.command, args: spec.args },\n },\n };\n return `${windsurfConfigPath()}\\n${JSON.stringify(block, null, 2)}`;\n}\n\nexport const windsurfClient: McpClient = {\n name: 'windsurf',\n label: 'Windsurf',\n\n async detect(): Promise<boolean> {\n return existsSync(path.join(homedir(), '.codeium', 'windsurf'));\n },\n\n async register(_cwd: string, spec: McpServerSpec): Promise<ChangeResult> {\n // Global config — never write into the user's homedir; print a snippet.\n return {\n path: windsurfConfigPath(),\n action: 'skip',\n description: `add manually (global config):\\n${snippet(spec)}`,\n };\n },\n\n async unregister(): Promise<void> {\n // No-op: Windsurf config is user-global and not managed by Substrata.\n },\n};\n","import type { ChangeResult } from '@substrata/core';\n\nimport { claudeCodeClient } from './claude-code';\nimport { cursorClient } from './cursor';\nimport { windsurfClient } from './windsurf';\n\n/**\n * MCP client registry (plan §14). A small table so new clients can be added\n * without touching the wizard flow. Each client can detect whether it is present,\n * register the Substrata MCP server (idempotent, dry-runnable), and unregister.\n */\n\n/** Spec for the Substrata MCP server entry written into client config. */\nexport type McpServerSpec = {\n /** Logical name of the server entry (the key in the client's config map). */\n name: string;\n command: string;\n args: string[];\n};\n\nexport type McpClient = {\n /** Stable id: \"claude\" | \"cursor\" | \"windsurf\". */\n name: string;\n /** Human-friendly label for prompts. */\n label: string;\n detect(cwd: string): Promise<boolean>;\n register(cwd: string, spec: McpServerSpec, dry?: boolean): Promise<ChangeResult>;\n unregister(cwd: string, name: string): Promise<void>;\n};\n\n/** The default Substrata MCP server spec written into client configs. */\nexport const SUBSTRATA_MCP_SPEC: McpServerSpec = {\n name: 'substrata',\n command: 'npx',\n args: ['-y', 'substrata-cli', 'mcp'],\n};\n\nexport const MCP_CLIENTS: McpClient[] = [claudeCodeClient, cursorClient, windsurfClient];\n\n/** Look up a client by its stable id. */\nexport function getMcpClient(name: string): McpClient | undefined {\n return MCP_CLIENTS.find((c) => c.name === name);\n}\n\n/** Detect which registered clients are present in/around `cwd`. */\nexport async function detectMcpClients(cwd: string): Promise<McpClient[]> {\n const detected: McpClient[] = [];\n for (const client of MCP_CLIENTS) {\n if (await client.detect(cwd)) detected.push(client);\n }\n return detected;\n}\n","import type { SearchResult } from '@substrata/core';\nimport pc from 'picocolors';\n\n/**\n * Human-readable rendering of search results and footprint lists. Output is\n * informative, not noisy: one block per result with id, title, status, tags,\n * path, and a snippet.\n */\n\nfunction statusLabel(status: SearchResult['status']): string {\n switch (status) {\n case 'superseded':\n return pc.yellow('[superseded]');\n case 'deprecated':\n return pc.red('[deprecated]');\n case 'draft':\n return pc.dim('[draft]');\n default:\n return '';\n }\n}\n\n/** Render a list of search results for the terminal. */\nexport function renderSearchResults(results: SearchResult[]): string {\n if (results.length === 0) {\n return pc.dim('No matching footprints or memory found.');\n }\n const blocks = results.map((r, i) => {\n const n = pc.dim(`${i + 1}.`);\n const status = statusLabel(r.status);\n const header = `${n} ${pc.bold(r.title || r.id)}${status ? ` ${status}` : ''}`;\n const lines = [header, ` ${pc.dim('id:')} ${r.id}`, ` ${pc.dim('path:')} ${r.filePath}`];\n if (r.tags.length > 0) lines.push(` ${pc.dim('tags:')} ${r.tags.join(', ')}`);\n if (r.snippet.trim().length > 0) {\n lines.push(` ${pc.dim(r.snippet.replace(/\\s+/g, ' ').trim())}`);\n }\n return lines.join('\\n');\n });\n return blocks.join('\\n\\n');\n}\n\n/** Render footprints (from `list`) as compact rows. */\nexport function renderFootprintList(\n rows: Array<{\n id: string;\n title: string;\n status: SearchResult['status'];\n createdAt?: string;\n tags: string[];\n }>,\n): string {\n if (rows.length === 0) {\n return pc.dim('No footprints found.');\n }\n return rows\n .map((r) => {\n const date = r.createdAt ? r.createdAt.slice(0, 10) : '----------';\n const status = statusLabel(r.status);\n const tags = r.tags.length > 0 ? pc.dim(` (${r.tags.join(', ')})`) : '';\n return `${pc.dim(date)} ${pc.bold(r.title || r.id)}${status ? ` ${status}` : ''}\\n ${pc.dim(r.id)}${tags}`;\n })\n .join('\\n');\n}\n","import { listFootprints, type Footprint } from '@substrata/core';\nimport type { Command } from 'commander';\n\nimport { renderFootprintList } from '../render/table';\nimport { out, requireConfig, resolveCwd } from '../util';\n\n/**\n * `substrata list` — list footprints filtered by tag/file/since. No index needed;\n * reads footprint files directly (plan §8.6).\n */\n\ntype ListOptions = {\n tag?: string;\n file?: string;\n since?: string;\n json?: boolean;\n};\n\nfunction matches(fp: Footprint, opts: ListOptions): boolean {\n if (opts.tag && !(fp.frontmatter.tags ?? []).includes(opts.tag)) return false;\n if (opts.file && !(fp.frontmatter.files_touched ?? []).includes(opts.file)) return false;\n if (opts.since && fp.frontmatter.created_at < opts.since) return false;\n return true;\n}\n\nexport function registerListCommand(program: Command): void {\n program\n .command('list')\n .description('List recent footprints')\n .option('--tag <tag>', 'Only footprints carrying this tag')\n .option('--file <path>', 'Only footprints touching this file')\n .option('--since <date>', 'Only footprints created on/after this ISO date')\n .option('--json', 'Output JSON')\n .action(async (opts: ListOptions, command: Command) => {\n const cwd = resolveCwd(command.parent?.opts());\n await requireConfig(cwd);\n\n const all = await listFootprints(cwd);\n const filtered = all.filter((fp) => matches(fp, opts));\n\n if (opts.json) {\n const rows = filtered.map((fp) => ({\n id: fp.frontmatter.id,\n title: fp.title,\n status: fp.frontmatter.status,\n createdAt: fp.frontmatter.created_at,\n tags: fp.frontmatter.tags ?? [],\n filesTouched: fp.frontmatter.files_touched ?? [],\n filePath: fp.filePath,\n }));\n out.plain(JSON.stringify(rows, null, 2));\n return;\n }\n\n out.plain(\n renderFootprintList(\n filtered.map((fp) => ({\n id: fp.frontmatter.id,\n title: fp.title,\n status: fp.frontmatter.status,\n createdAt: fp.frontmatter.created_at,\n tags: fp.frontmatter.tags ?? [],\n })),\n ),\n );\n });\n}\n","import type { Command } from 'commander';\n\nimport { CliError, resolveCwd } from '../util';\n\n/**\n * `substrata mcp` — run the MCP server (plan §8.12). Dynamically imports\n * `@substrata/mcp-server` (built in parallel) and calls its documented contract:\n * runMcpServer(options?: { cwd?: string }): Promise<void>\n */\n\ntype McpServerModule = {\n runMcpServer: (options?: { cwd?: string }) => Promise<void>;\n};\n\nexport function registerMcpCommand(program: Command): void {\n program\n .command('mcp')\n .description('Run the Substrata MCP server (stdio)')\n .action(async (_opts: unknown, command: Command) => {\n const cwd = resolveCwd(command.parent?.opts());\n let mod: McpServerModule;\n try {\n mod = (await import('@substrata/mcp-server')) as unknown as McpServerModule;\n } catch (err) {\n throw new CliError(`Failed to load @substrata/mcp-server: ${(err as Error).message}`);\n }\n if (typeof mod.runMcpServer !== 'function') {\n throw new CliError('@substrata/mcp-server does not export runMcpServer().');\n }\n await mod.runMcpServer({ cwd });\n });\n}\n","import { existsSync } from 'node:fs';\nimport { readFile, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\n\nimport {\n appendMemoryEntries,\n existingEntryIds,\n listFootprints,\n memoryDir,\n type MemoryEntry,\n} from '@substrata/core';\nimport type { Command } from 'commander';\n\nimport { promptConfirm, isNonInteractive } from '../wizard/prompts';\nimport { out, requireConfig, resolveCwd } from '../util';\n\n/**\n * `substrata memory update` — scan footprints, extract `Memory learned` sections,\n * suggest entries, confirm, and append them before the entries:end marker of\n * `.substrata/memory/conventions.md` (created with frontmatter if missing).\n * Idempotent: entries whose source footprint id already appears are skipped\n * (plan §8.10).\n */\n\nconst CONVENTIONS_FRONTMATTER = `---\nschema_version: 1\nid: mem_repo_conventions\ntype: repo_conventions\ntags:\n - conventions\n---\n\n# Repo conventions\n\nCurated, durable knowledge agents should read often.\n\n<!-- substrata:entries:start -->\n<!-- substrata:entries:end -->\n`;\n\ntype MemoryUpdateOptions = {\n since?: string;\n yes?: boolean;\n};\n\nexport function registerMemoryUpdateCommand(program: Command): void {\n const memory = program.command('memory').description('Curated memory utilities');\n\n memory\n .command('update')\n .description('Suggest and append memory entries from footprints')\n .option('--since <date>', 'Only footprints created on/after this ISO date')\n .option('--yes', 'Append without confirmation')\n .action(async (opts: MemoryUpdateOptions, command: Command) => {\n // `memory` is the parent of `update`; the program is its grandparent.\n const cwd = resolveCwd(command.parent?.parent?.opts());\n await requireConfig(cwd);\n\n const footprints = await listFootprints(cwd);\n const filtered = opts.since\n ? footprints.filter((fp) => fp.frontmatter.created_at >= opts.since!)\n : footprints;\n\n const candidates: MemoryEntry[] = [];\n for (const fp of filtered) {\n const learned = fp.sections.memoryLearned ?? [];\n if (learned.length === 0) continue;\n candidates.push({\n sourceId: fp.frontmatter.id,\n lines: learned.map((l) => `- ${l}`),\n });\n }\n\n if (candidates.length === 0) {\n out.info('No `Memory learned` sections found in the selected footprints.');\n return;\n }\n\n const filePath = path.join(memoryDir(cwd), 'conventions.md');\n\n // Compute which entries are genuinely new (idempotent preview).\n const alreadyPresent = existsSync(filePath)\n ? existingEntryIds(await readFile(filePath, 'utf8'))\n : new Set<string>();\n const fresh = candidates.filter((c) => !alreadyPresent.has(c.sourceId));\n\n if (fresh.length === 0) {\n out.info('All suggested entries are already present. Nothing to do.');\n return;\n }\n\n out.plain('Suggested memory entries:');\n for (const entry of fresh) {\n out.plain(` [${entry.sourceId}]`);\n for (const line of entry.lines) out.plain(` ${line}`);\n }\n\n const confirmed =\n opts.yes || isNonInteractive()\n ? true\n : await promptConfirm({\n message: `Append ${fresh.length} entr${fresh.length === 1 ? 'y' : 'ies'} to conventions.md?`,\n defaultValue: true,\n });\n\n if (!confirmed) {\n out.info('Aborted. Nothing written.');\n return;\n }\n\n if (!existsSync(filePath)) {\n await writeFile(filePath, CONVENTIONS_FRONTMATTER, 'utf8');\n }\n\n const changed = await appendMemoryEntries(filePath, candidates);\n if (changed) {\n out.ok(`Appended ${fresh.length} memory entr${fresh.length === 1 ? 'y' : 'ies'}.`);\n } else {\n out.info('No new entries appended (already present).');\n }\n });\n}\n","import { search } from '@substrata/search';\nimport type { Command } from 'commander';\n\nimport { renderSearchResults } from '../render/table';\nimport { out, requireConfig, resolveCwd } from '../util';\n\nimport { ensureFreshIndex } from './auto-index';\n\n/**\n * `substrata search <query>` — full-text search over footprints + memory.\n * Auto-(re)builds a stale/missing index unless `--no-auto-index`. By default\n * includes superseded footprints (demoted in ranking) so humans can trace\n * history; `--exclude-superseded` drops them (plan §8.3).\n */\n\ntype SearchOptions = {\n json?: boolean;\n files?: string[];\n tag?: string[];\n limit?: string;\n excludeSuperseded?: boolean;\n autoIndex?: boolean;\n};\n\nfunction collect(value: string, previous: string[] = []): string[] {\n return [...previous, value];\n}\n\nexport function registerSearchCommand(program: Command): void {\n program\n .command('search <query>')\n .description('Search footprints and memory')\n .option('--json', 'Output JSON')\n .option('--files <path>', 'Filter to docs touching this file (repeatable)', collect, [])\n .option('--tag <tag>', 'Filter to docs carrying this tag (repeatable)', collect, [])\n .option('--limit <n>', 'Maximum number of results')\n .option('--exclude-superseded', 'Drop superseded/deprecated footprints')\n .option('--no-auto-index', 'Do not auto-(re)build a stale/missing index')\n .action(async (query: string, opts: SearchOptions, command: Command) => {\n const cwd = resolveCwd(command.parent?.opts());\n const config = await requireConfig(cwd);\n\n await ensureFreshIndex(cwd, opts.autoIndex !== false);\n\n const limit = opts.limit ? Number(opts.limit) : config.search.default_limit;\n const results = await search(query, {\n cwd,\n limit: Number.isFinite(limit) ? limit : config.search.default_limit,\n files: opts.files && opts.files.length > 0 ? opts.files : undefined,\n tags: opts.tag && opts.tag.length > 0 ? opts.tag : undefined,\n excludeSuperseded: opts.excludeSuperseded,\n });\n\n if (opts.json) {\n out.plain(JSON.stringify(results, null, 2));\n return;\n }\n out.plain(renderSearchResults(results));\n });\n}\n","import { findFootprintById } from '@substrata/core';\nimport type { Command } from 'commander';\nimport pc from 'picocolors';\n\nimport { CliError, out, requireConfig, resolveCwd } from '../util';\n\n/**\n * `substrata show <id>` — pretty-print one footprint, or its JSON / path.\n * (plan §8.7)\n */\n\ntype ShowOptions = {\n json?: boolean;\n path?: boolean;\n};\n\nexport function registerShowCommand(program: Command): void {\n program\n .command('show <id>')\n .description('Show one footprint')\n .option('--json', 'Output the parsed footprint as JSON')\n .option('--path', 'Print only the footprint file path')\n .action(async (id: string, opts: ShowOptions, command: Command) => {\n const cwd = resolveCwd(command.parent?.opts());\n await requireConfig(cwd);\n\n const fp = await findFootprintById(cwd, id);\n if (!fp) throw new CliError(`Footprint not found: ${id}`);\n\n if (opts.path) {\n out.plain(fp.filePath);\n return;\n }\n\n if (opts.json) {\n out.plain(\n JSON.stringify(\n {\n id: fp.frontmatter.id,\n title: fp.title,\n filePath: fp.filePath,\n frontmatter: fp.frontmatter,\n sections: fp.sections,\n },\n null,\n 2,\n ),\n );\n return;\n }\n\n const fm = fp.frontmatter;\n const lines: string[] = [\n pc.bold(fp.title || fm.id),\n `${pc.dim('id:')} ${fm.id}`,\n `${pc.dim('status:')} ${fm.status}`,\n `${pc.dim('work_type:')} ${fm.work_type}`,\n `${pc.dim('actor:')} ${fm.actor}`,\n ];\n if (fm.requester) lines.push(`${pc.dim('requester:')} ${fm.requester}`);\n if (fm.agent_model) lines.push(`${pc.dim('model:')} ${fm.agent_model}`);\n lines.push(`${pc.dim('created:')} ${fm.created_at}`);\n if (fm.tags && fm.tags.length > 0)\n lines.push(`${pc.dim('tags:')} ${fm.tags.join(', ')}`);\n if (fm.files_touched && fm.files_touched.length > 0) {\n lines.push(`${pc.dim('files:')} ${fm.files_touched.join(', ')}`);\n }\n lines.push(`${pc.dim('path:')} ${fp.filePath}`);\n lines.push('');\n lines.push(fp.body.trim());\n out.plain(lines.join('\\n'));\n });\n}\n","import { NotFoundError, supersedeFootprint } from '@substrata/core';\nimport { buildIndex } from '@substrata/search';\nimport type { Command } from 'commander';\n\nimport { CliError, out, requireConfig, resolveCwd } from '../util';\n\n/**\n * `substrata supersede <old-id> --by <new-id>` — mark an old footprint replaced\n * by a new one (frontmatter-only edits), then rebuild the index so ranking\n * reflects the new status (plan §8.9).\n */\nexport function registerSupersedeCommand(program: Command): void {\n program\n .command('supersede <old-id>')\n .description('Mark an old footprint as superseded by a new one')\n .requiredOption('--by <new-id>', 'Id of the footprint that replaces the old one')\n .action(async (oldId: string, opts: { by: string }, command: Command) => {\n const cwd = resolveCwd(command.parent?.opts());\n await requireConfig(cwd);\n\n try {\n await supersedeFootprint(cwd, oldId, opts.by);\n } catch (err) {\n if (err instanceof NotFoundError) throw new CliError(err.message);\n throw err;\n }\n\n await buildIndex(cwd);\n out.ok(`${oldId} superseded by ${opts.by}. Index rebuilt.`);\n });\n}\n"],"mappings":"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","names":["pc","collect","path","path","readFile","existsSync","readFileSync","homedir","path","pc","execFile","existsSync","path","promisify","readFileSync","path","execFileAsync","promisify","execFile","path","existsSync","existsSync","path","path","existsSync","existsSync","path","homedir","path","existsSync","readFileSync","pc","readFile","collect","pc","existsSync","readFile","writeFile","path","path","existsSync","readFile","writeFile","collect","pc","pc","pc"]}
|