diff --git a/.agents/skills/oat-project-complete/SKILL.md b/.agents/skills/oat-project-complete/SKILL.md index 08d9a95dd..68004c156 100644 --- a/.agents/skills/oat-project-complete/SKILL.md +++ b/.agents/skills/oat-project-complete/SKILL.md @@ -1,6 +1,6 @@ --- name: oat-project-complete -version: 1.3.0 +version: 1.3.1 description: Use when all implementation work is finished and the project is ready to close. Marks the OAT project lifecycle as complete. disable-model-invocation: true user-invocable: true @@ -192,7 +192,7 @@ Rules: ### Step 5: Set Lifecycle Complete -Update state.md frontmatter to add/update `oat_lifecycle: complete` and set completion timestamp: +Update `state.md` frontmatter to add/update `oat_lifecycle: complete` and set completion timestamps: ```bash STATE_FILE="${PROJECT_PATH}/state.md" @@ -216,6 +216,18 @@ sed -E "s/^oat_project_state_updated:.*/oat_project_state_updated: \"$NOW_UTC\"/ mv "$STATE_FILE.tmp" "$STATE_FILE" ``` +Then update the markdown body in `state.md` so the completion state is explicit and does not rely on reference lookups: + +- Set `**Status:** Complete` +- Set `**Last Updated:**` to the completion date in `YYYY-MM-DD` +- In `## Current Phase`, replace the body with: + - `Lifecycle complete; archived locally` when the project is archived in Step 8 + - `Lifecycle complete` when the project is completed without archive +- In `## Progress`, preserve the existing completed workflow/review bullets and add `- ✓ Project lifecycle complete` if it is not already present +- In `## Next Milestone`, replace the body with `None. Project complete.` + +Do not infer these body mutations from other archived projects. Apply them directly as part of this skill. + ### Step 6: Clear Active Project Pointer Clear the active project pointer immediately. If the user is completing a project, clearing the pointer is implicit — no confirmation needed. diff --git a/.oat/repo/reference/backlog/index.md b/.oat/repo/reference/backlog/index.md index 157abb9f0..25f0a3c96 100644 --- a/.oat/repo/reference/backlog/index.md +++ b/.oat/repo/reference/backlog/index.md @@ -5,7 +5,7 @@ ## Curated Overview - `bl-42f9` tracks the only in-progress backlog item and is currently being delivered through the active `local-project-management` project. -- Inbox work is concentrated on workflow operations: optional S3 archival for project completion and a Jira-oriented backlog refinement flow. +- Inbox work is concentrated on workflow operations: project-completion hardening (`bl-0ace`, `bl-ea64`) and a Jira-oriented backlog refinement flow. - Planned follow-on investments cluster around provider ergonomics (`bl-cbdd`), review collaboration (`bl-9fb8`), dependency analysis (`bl-3327`), and ideas-to-project promotion (`bl-b3f7`). - Longer-horizon backlog work now includes explicit entries for freshness hardening (`bl-f9bd`) and memory/provider-enhancement work (`bl-71a1`). @@ -18,6 +18,7 @@ | bl-b3f7 | Add idea promotion and auto-discovery flow to oat-project-new | open | medium | feature | L | | bl-9fb8 | Add PR review follow-on skill set (provide-remote, respond-remote, summarize-remote) | open | medium | feature | L | | bl-ff5d | Backlog Refinement Flow (Jira ticket generation) | open | medium | feature | L | +| bl-0ace | Move oat-project-complete state mutations into a CLI helper | open | medium | feature | M | | bl-cbdd | Optional Codex prompt-wrapper generation for synced OAT skills | open | medium | feature | M | | bl-ea64 | Optional S3 archival in oat-project-complete workflow | open | medium | feature | L | | bl-f9bd | Staleness + knowledge drift upgrades | open | medium | feature | L | diff --git a/.oat/repo/reference/backlog/items/project-complete-cli-helper.md b/.oat/repo/reference/backlog/items/project-complete-cli-helper.md new file mode 100644 index 000000000..8bdc5dccd --- /dev/null +++ b/.oat/repo/reference/backlog/items/project-complete-cli-helper.md @@ -0,0 +1,26 @@ +--- +id: bl-0ace +title: 'Move oat-project-complete state mutations into a CLI helper' +status: open +priority: medium +scope: feature +scope_estimate: M +labels: ['workflow', 'cli'] +assignee: null +created: '2026-03-21T00:28:34Z' +updated: '2026-03-21T00:28:34Z' +associated_issues: [] +oat_template: true +oat_template_name: backlog-item +--- + +## Description + +`oat-project-complete` currently has to encode the exact `state.md` completion mutations in the skill body, including markdown body updates that are easy to drift from the canonical project state shape. The completion flow should move those state mutations into a CLI-owned helper so the skill can delegate to one implementation instead of carrying formatting rules and inferred conventions. + +## Acceptance Criteria + +- A CLI-owned helper or command updates project completion state in the canonical shape, including both frontmatter and markdown body mutations. +- `oat-project-complete` delegates the state mutation work to the CLI helper instead of hardcoding the completion-state formatting contract. +- Completing a project no longer requires checking archived project state files to infer the expected output shape. +- Tests cover the resulting completion-state format and protect against drift between the CLI behavior and the skill guidance. diff --git a/.oat/repo/reference/current-state.md b/.oat/repo/reference/current-state.md index 94e1b434f..7719ff742 100644 --- a/.oat/repo/reference/current-state.md +++ b/.oat/repo/reference/current-state.md @@ -130,7 +130,7 @@ This document is a birdseye view of where OAT is _right now_ in `open-agent-tool - `oat providers list`, `oat providers inspect`, `oat providers set` - `oat cleanup project`, `oat cleanup artifacts` - `oat instructions validate`, `oat instructions sync` - - `oat backlog generate-id`, `oat backlog regenerate-index` + - `oat backlog init`, `oat backlog generate-id`, `oat backlog regenerate-index` - `oat tools list`, `oat tools outdated`, `oat tools info`, `oat tools update`, `oat tools remove`, `oat tools install` (packs: core, ideas, workflows, utility, project-management, research) - Provider config model: - Project provider enablement lives in `.oat/sync/config.json` (`providers..enabled`). @@ -232,7 +232,8 @@ Backlog/reference workflow quickstart: 1. Create or update backlog items: - `oat-pjm-add-backlog-item` -2. Regenerate managed backlog metadata directly when needed: +2. Scaffold or regenerate managed backlog metadata directly when needed: + - `oat backlog init` - `oat backlog generate-id ` - `oat backlog regenerate-index` 3. Refresh repo references: diff --git a/apps/oat-docs/docs/guide/cli-reference.md b/apps/oat-docs/docs/guide/cli-reference.md index db374e9ed..49d858494 100644 --- a/apps/oat-docs/docs/guide/cli-reference.md +++ b/apps/oat-docs/docs/guide/cli-reference.md @@ -67,11 +67,12 @@ See [Tool Packs](tool-packs.md) for the pack lifecycle and compatibility notes. Use the `oat backlog` group when you want direct CLI support for the file-backed backlog under `.oat/repo/reference/backlog/`. +- `oat backlog init` - scaffold `.oat/repo/reference/backlog/` with starter files and directories for a fresh repo - `oat backlog generate-id ` - generate a unique backlog ID from a filename seed - `oat backlog generate-id --created-at ` - generate a reproducible ID for a known creation timestamp - `oat backlog regenerate-index` - rebuild the managed backlog index table from item frontmatter -This command group is primarily used by the `oat-pjm-*` project-management skills, but it is also available directly when you need to inspect or repair backlog metadata by hand. +Run `oat backlog init` first when the local backlog scaffold does not exist yet in a fresh repo. This command group is primarily used by the `oat-pjm-*` project-management skills, but it is also available directly when you need to inspect or repair backlog metadata by hand. ### `oat local ...` diff --git a/apps/oat-docs/docs/quickstart.md b/apps/oat-docs/docs/quickstart.md index 1d4ded1e3..231971e8a 100644 --- a/apps/oat-docs/docs/quickstart.md +++ b/apps/oat-docs/docs/quickstart.md @@ -87,6 +87,7 @@ pnpm run cli -- docs init --app-name my-docs pnpm run cli -- docs nav sync --target-dir apps/my-docs # Manage the file-backed backlog directly +pnpm run cli -- backlog init pnpm run cli -- backlog generate-id add-webhook-support --created-at 2026-03-15T14:30:00Z pnpm run cli -- backlog regenerate-index @@ -113,6 +114,7 @@ oat instructions sync oat remove skills --pack utility oat doctor --scope all oat project new my-project --mode spec-driven +oat backlog init oat backlog regenerate-index ``` diff --git a/packages/cli/src/commands/backlog/index.test.ts b/packages/cli/src/commands/backlog/index.test.ts new file mode 100644 index 000000000..0d5e52cea --- /dev/null +++ b/packages/cli/src/commands/backlog/index.test.ts @@ -0,0 +1,127 @@ +import type { CommandContext, GlobalOptions } from '@app/command-context'; +import { + createLoggerCapture, + type LoggerCapture, +} from '@commands/__tests__/helpers'; +import { Command } from 'commander'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + +import { createBacklogCommand } from './index'; + +function createHarness(): { + capture: LoggerCapture; + command: Command; + initializeBacklog: ReturnType; + resolveProjectRoot: ReturnType; +} { + const capture = createLoggerCapture(); + const initializeBacklog = vi.fn(async (_backlogRoot: string) => {}); + const resolveProjectRoot = vi.fn( + async (_cwd: string) => '/tmp/workspace/repo', + ); + + const command = createBacklogCommand({ + buildCommandContext: (globalOptions: GlobalOptions): CommandContext => ({ + scope: (globalOptions.scope ?? 'all') as CommandContext['scope'], + dryRun: false, + verbose: globalOptions.verbose ?? false, + json: globalOptions.json ?? false, + cwd: globalOptions.cwd ?? '/tmp/workspace', + home: '/tmp/home', + interactive: !(globalOptions.json ?? false), + logger: capture.logger, + }), + initializeBacklog, + resolveProjectRoot, + }); + + return { + capture, + command, + initializeBacklog, + resolveProjectRoot, + }; +} + +async function runCommand( + command: Command, + globalArgs: string[] = [], + cmdArgs: string[] = [], +): Promise { + const program = new Command() + .name('oat') + .option('--json') + .option('--verbose') + .option('--cwd ') + .exitOverride(); + + program.addCommand(command); + + await program.parseAsync([...globalArgs, 'backlog', 'init', ...cmdArgs], { + from: 'user', + }); +} + +describe('createBacklogCommand', () => { + let originalExitCode: number | undefined; + + beforeEach(() => { + originalExitCode = process.exitCode; + process.exitCode = undefined; + }); + + afterEach(() => { + process.exitCode = originalExitCode; + }); + + it('initializes the default backlog root resolved from the project root', async () => { + const { command, capture, initializeBacklog, resolveProjectRoot } = + createHarness(); + + await runCommand(command); + + expect(resolveProjectRoot).toHaveBeenCalledWith('/tmp/workspace'); + expect(initializeBacklog).toHaveBeenCalledWith( + '/tmp/workspace/repo/.oat/repo/reference/backlog', + ); + expect(capture.info).toContain( + 'Initialized backlog scaffold at /tmp/workspace/repo/.oat/repo/reference/backlog', + ); + expect(process.exitCode).toBe(0); + }); + + it('uses the configured backlog root override relative to cwd', async () => { + const { command, capture, initializeBacklog, resolveProjectRoot } = + createHarness(); + + await runCommand( + command, + ['--cwd', '/tmp/override-workspace'], + ['--backlog-root', 'custom/backlog'], + ); + + expect(resolveProjectRoot).not.toHaveBeenCalled(); + expect(initializeBacklog).toHaveBeenCalledWith( + '/tmp/override-workspace/custom/backlog', + ); + expect(capture.info).toContain( + 'Initialized backlog scaffold at /tmp/override-workspace/custom/backlog', + ); + expect(process.exitCode).toBe(0); + }); + + it('outputs structured JSON for backlog init', async () => { + const { command, capture, initializeBacklog } = createHarness(); + + await runCommand(command, ['--json']); + + expect(initializeBacklog).toHaveBeenCalledWith( + '/tmp/workspace/repo/.oat/repo/reference/backlog', + ); + expect(capture.jsonPayloads[0]).toEqual({ + status: 'ok', + backlogRoot: '/tmp/workspace/repo/.oat/repo/reference/backlog', + }); + expect(process.exitCode).toBe(0); + }); +}); diff --git a/packages/cli/src/commands/backlog/index.ts b/packages/cli/src/commands/backlog/index.ts index 33daba856..043e2f065 100644 --- a/packages/cli/src/commands/backlog/index.ts +++ b/packages/cli/src/commands/backlog/index.ts @@ -5,12 +5,17 @@ import { readGlobalOptions } from '@commands/shared/shared.utils'; import { resolveProjectRoot } from '@fs/paths'; import { Command } from 'commander'; +import { initializeBacklog } from './init'; import { regenerateBacklogIndex } from './regenerate-index'; import { generateUniqueBacklogId, readExistingBacklogIds, } from './shared/generate-id'; +interface InitOptions { + backlogRoot?: string; +} + interface RegenerateIndexOptions { backlogRoot?: string; } @@ -22,12 +27,14 @@ interface GenerateIdOptions { interface BacklogCommandDependencies { buildCommandContext: typeof buildCommandContext; resolveProjectRoot: typeof resolveProjectRoot; + initializeBacklog: typeof initializeBacklog; regenerateBacklogIndex: typeof regenerateBacklogIndex; } const DEFAULT_DEPENDENCIES: BacklogCommandDependencies = { buildCommandContext, resolveProjectRoot, + initializeBacklog, regenerateBacklogIndex, }; @@ -56,6 +63,34 @@ export function createBacklogCommand( 'Manage file-backed backlog items and indexes', ); + cmd + .command('init') + .description( + 'Scaffold the canonical backlog directory structure and starter files', + ) + .option( + '--backlog-root ', + 'Backlog root directory (defaults to .oat/repo/reference/backlog)', + ) + .action(async (options: InitOptions, command: Command) => { + const context = dependencies.buildCommandContext( + readGlobalOptions(command), + ); + const backlogRoot = await resolveBacklogRoot( + context, + options.backlogRoot, + dependencies, + ); + await dependencies.initializeBacklog(backlogRoot); + + if (context.json) { + context.logger.json({ status: 'ok', backlogRoot }); + } else { + context.logger.info(`Initialized backlog scaffold at ${backlogRoot}`); + } + process.exitCode = 0; + }); + cmd .command('regenerate-index') .description('Regenerate the managed backlog index table') diff --git a/packages/cli/src/commands/backlog/init.test.ts b/packages/cli/src/commands/backlog/init.test.ts new file mode 100644 index 000000000..5787c7ea2 --- /dev/null +++ b/packages/cli/src/commands/backlog/init.test.ts @@ -0,0 +1,105 @@ +import { access, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; + +import { afterEach, describe, expect, it } from 'vitest'; + +import { initializeBacklog } from './init'; + +describe('initializeBacklog', () => { + const tempDirs: string[] = []; + + afterEach(async () => { + await Promise.all( + tempDirs.map((dir) => rm(dir, { recursive: true, force: true })), + ); + tempDirs.length = 0; + }); + + it('creates the backlog directories and starter markdown files', async () => { + const backlogRoot = await mkdtemp(join(tmpdir(), 'oat-backlog-init-')); + tempDirs.push(backlogRoot); + + await initializeBacklog(backlogRoot); + + await expect(access(join(backlogRoot, 'items'))).resolves.toBeUndefined(); + await expect( + access(join(backlogRoot, 'archived')), + ).resolves.toBeUndefined(); + await expect( + access(join(backlogRoot, 'items', '.gitkeep')), + ).resolves.toBeUndefined(); + await expect( + access(join(backlogRoot, 'archived', '.gitkeep')), + ).resolves.toBeUndefined(); + + const index = await readFile(join(backlogRoot, 'index.md'), 'utf8'); + expect(index).toContain('# OAT Backlog Index'); + expect(index).toContain('## Curated Overview'); + expect(index).toContain(''); + expect(index).toContain(''); + expect(index).toContain('## Notes'); + + const completed = await readFile(join(backlogRoot, 'completed.md'), 'utf8'); + expect(completed).toContain('# OAT Backlog Completed'); + expect(completed).toContain('## Entry Format'); + expect(completed).toContain('## Completed Items'); + }); + + it('does not overwrite existing backlog markdown files on rerun', async () => { + const backlogRoot = await mkdtemp(join(tmpdir(), 'oat-backlog-init-')); + tempDirs.push(backlogRoot); + + await initializeBacklog(backlogRoot); + + const indexPath = join(backlogRoot, 'index.md'); + const completedPath = join(backlogRoot, 'completed.md'); + const customIndex = '# Custom Backlog Index\n'; + const customCompleted = '# Custom Completed\n'; + + await writeFile(indexPath, customIndex, 'utf8'); + await writeFile(completedPath, customCompleted, 'utf8'); + + await initializeBacklog(backlogRoot); + + await expect(readFile(indexPath, 'utf8')).resolves.toBe(customIndex); + await expect(readFile(completedPath, 'utf8')).resolves.toBe( + customCompleted, + ); + }); + + it('preserves curated overview edits when the scaffold runs again', async () => { + const backlogRoot = await mkdtemp(join(tmpdir(), 'oat-backlog-init-')); + tempDirs.push(backlogRoot); + + await initializeBacklog(backlogRoot); + + const indexPath = join(backlogRoot, 'index.md'); + const index = await readFile(indexPath, 'utf8'); + const updatedIndex = index.replace( + '- Add brief narrative summaries here as backlog items are created and reprioritized.', + '- Keep this curated summary.', + ); + await writeFile(indexPath, updatedIndex, 'utf8'); + + await initializeBacklog(backlogRoot); + + await expect(readFile(indexPath, 'utf8')).resolves.toContain( + '- Keep this curated summary.', + ); + }); + + it('does not overwrite existing directory placeholders on rerun', async () => { + const backlogRoot = await mkdtemp(join(tmpdir(), 'oat-backlog-init-')); + tempDirs.push(backlogRoot); + + await initializeBacklog(backlogRoot); + + const itemsPlaceholder = join(backlogRoot, 'items', '.gitkeep'); + await writeFile(itemsPlaceholder, 'keep me\n', 'utf8'); + + await initializeBacklog(backlogRoot); + + await expect(readFile(itemsPlaceholder, 'utf8')).resolves.toBe('keep me\n'); + }); +}); diff --git a/packages/cli/src/commands/backlog/init.ts b/packages/cli/src/commands/backlog/init.ts new file mode 100644 index 000000000..76bfc72c9 --- /dev/null +++ b/packages/cli/src/commands/backlog/init.ts @@ -0,0 +1,87 @@ +import { access, mkdir, writeFile } from 'node:fs/promises'; +import { join } from 'node:path'; + +const INDEX_START = ''; +const INDEX_END = ''; +const DIRECTORY_PLACEHOLDER = '.gitkeep'; + +const EMPTY_MANAGED_TABLE = [ + INDEX_START, + '', + '| ID | Title | Status | Priority | Scope | Estimate |', + '| --- | --- | --- | --- | --- | --- |', + '| _No backlog items yet_ | - | - | - | - | - |', + '', + INDEX_END, +].join('\n'); + +const STARTER_INDEX = [ + '# OAT Backlog Index', + '', + '> Generated backlog table lives inside the managed section below. Keep curated narrative updates in the overview section so CLI regeneration stays safe.', + '', + '## Curated Overview', + '', + '- Add brief narrative summaries here as backlog items are created and reprioritized.', + '', + EMPTY_MANAGED_TABLE, + '', + '## Notes', + '', + '- Active item files live in `backlog/items/`', + '- Archived item files live in `backlog/archived/`', + '- Historical completions are summarized in `backlog/completed.md`', + '', +].join('\n'); + +const STARTER_COMPLETED = [ + '# OAT Backlog Completed', + '', + '> Summary archive for completed backlog work. Keep newest entries first. Use `backlog/archived/` for full file-per-item historical records when a completed item still needs rich context.', + '', + '## Entry Format', + '', + '- `YYYY-MM-DD — bl-XXXX — Title — one-line outcome summary`', + '', + '## Completed Items', + '', +].join('\n'); + +async function writeFileIfMissing( + filePath: string, + content: string, +): Promise { + try { + await access(filePath); + } catch (error) { + const code = + error && typeof error === 'object' && 'code' in error + ? String(error.code) + : null; + + if (code !== 'ENOENT') { + throw error; + } + + await writeFile(filePath, content, 'utf8'); + } +} + +export async function initializeBacklog(backlogRoot: string): Promise { + await mkdir(join(backlogRoot, 'items'), { recursive: true }); + await mkdir(join(backlogRoot, 'archived'), { recursive: true }); + await writeFileIfMissing( + join(backlogRoot, 'items', DIRECTORY_PLACEHOLDER), + '', + ); + await writeFileIfMissing( + join(backlogRoot, 'archived', DIRECTORY_PLACEHOLDER), + '', + ); + + await writeFileIfMissing(join(backlogRoot, 'index.md'), STARTER_INDEX); + await writeFileIfMissing( + join(backlogRoot, 'completed.md'), + STARTER_COMPLETED, + ); +} diff --git a/packages/cli/src/commands/backlog/regenerate-index.test.ts b/packages/cli/src/commands/backlog/regenerate-index.test.ts index 7883a35b4..edaf54785 100644 --- a/packages/cli/src/commands/backlog/regenerate-index.test.ts +++ b/packages/cli/src/commands/backlog/regenerate-index.test.ts @@ -1,9 +1,11 @@ +import { execFileSync } from 'node:child_process'; import { mkdtemp, mkdir, readFile, rm, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { afterEach, describe, expect, it } from 'vitest'; +import { initializeBacklog } from './init'; import { regenerateBacklogIndex } from './regenerate-index'; const INDEX_START = ''; @@ -140,4 +142,81 @@ describe('regenerateBacklogIndex', () => { expect(index).toContain('| _No backlog items yet_ | - | - | - | - | - |'); }); + + it('works with a freshly scaffolded backlog root and preserves curated overview content', async () => { + const backlogRoot = await mkdtemp(join(tmpdir(), 'oat-backlog-seeded-')); + tempDirs.push(backlogRoot); + + await initializeBacklog(backlogRoot); + + const itemsDir = join(backlogRoot, 'items'); + await writeBacklogItem(itemsDir, 'alpha.md', { + id: 'bl-aaaa', + title: '"Alpha"', + status: 'open', + priority: 'high', + scope: 'feature', + scope_estimate: 'M', + }); + + const indexPath = join(backlogRoot, 'index.md'); + const originalIndex = await readFile(indexPath, 'utf8'); + await writeFile( + indexPath, + originalIndex.replace( + '- Add brief narrative summaries here as backlog items are created and reprioritized.', + '- Keep this curated summary.', + ), + 'utf8', + ); + + await regenerateBacklogIndex(backlogRoot); + + const index = await readFile(indexPath, 'utf8'); + expect(index).toContain('- Keep this curated summary.'); + expect(index).toContain('| bl-aaaa | Alpha | open | high | feature | M |'); + expect(index).not.toContain( + '| _No backlog items yet_ | - | - | - | - | - |', + ); + }); + + it('works after a git commit and clone round-trip without rerunning init', async () => { + const tempRoot = await mkdtemp(join(tmpdir(), 'oat-backlog-git-')); + tempDirs.push(tempRoot); + + const repoRoot = join(tempRoot, 'repo'); + const cloneRoot = join(tempRoot, 'clone'); + const backlogRoot = join(repoRoot, '.oat', 'repo', 'reference', 'backlog'); + + await mkdir(repoRoot, { recursive: true }); + await initializeBacklog(backlogRoot); + + execFileSync('git', ['init', '-q'], { cwd: repoRoot }); + execFileSync('git', ['config', 'user.email', 'review@example.com'], { + cwd: repoRoot, + }); + execFileSync('git', ['config', 'user.name', 'reviewer'], { cwd: repoRoot }); + execFileSync('git', ['add', '.'], { cwd: repoRoot }); + execFileSync('git', ['commit', '-qm', 'init backlog scaffold'], { + cwd: repoRoot, + }); + execFileSync('git', ['clone', '-q', repoRoot, cloneRoot], { + cwd: tempRoot, + }); + + const clonedBacklogRoot = join( + cloneRoot, + '.oat', + 'repo', + 'reference', + 'backlog', + ); + + await expect( + readFile(join(clonedBacklogRoot, 'items', '.gitkeep'), 'utf8'), + ).resolves.toBe(''); + await expect( + regenerateBacklogIndex(clonedBacklogRoot), + ).resolves.toBeUndefined(); + }); }); diff --git a/packages/cli/src/commands/help-snapshots.test.ts b/packages/cli/src/commands/help-snapshots.test.ts index bcebcd550..d10b43014 100644 --- a/packages/cli/src/commands/help-snapshots.test.ts +++ b/packages/cli/src/commands/help-snapshots.test.ts @@ -106,6 +106,47 @@ describe('help output snapshots', () => { `); }); + it('backlog --help matches snapshot', () => { + const program = createRegisteredProgram(); + const help = getCommandByPath(program, ['backlog']).helpInformation(); + expect(help).toMatchInlineSnapshot(` + "Usage: oat backlog [options] [command] + + Manage file-backed backlog items and indexes + + Options: + -h, --help display help for command + + Commands: + init [options] Scaffold the canonical backlog directory + structure and starter files + regenerate-index [options] Regenerate the managed backlog index table + generate-id [options] Generate a backlog item identifier from a + filename seed + help [command] display help for command + " + `); + }); + + it('backlog init --help matches snapshot', () => { + const program = createRegisteredProgram(); + const help = getCommandByPath(program, [ + 'backlog', + 'init', + ]).helpInformation(); + expect(help).toMatchInlineSnapshot(` + "Usage: oat backlog init [options] + + Scaffold the canonical backlog directory structure and starter files + + Options: + --backlog-root Backlog root directory (defaults to + .oat/repo/reference/backlog) + -h, --help display help for command + " + `); + }); + it('status --help matches snapshot', () => { const program = createRegisteredProgram(); const help = getCommandByPath(program, ['status']).helpInformation();