Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
0d7410b
chore(local): normalize expand regex escaping
tkstang Mar 20, 2026
9e91036
feat(p01-t01): add backlog scaffold initializer
tkstang Mar 20, 2026
2c6b6fc
chore(oat): update tracking artifacts for p01-t01
tkstang Mar 20, 2026
2c097f9
feat(p01-t02): add backlog init command
tkstang Mar 20, 2026
941980c
chore(oat): update tracking artifacts for p01-t02
tkstang Mar 20, 2026
10d4d3d
test(p02-t01): cover backlog init compatibility
tkstang Mar 20, 2026
d12cdc8
chore(oat): update tracking artifacts for implementation complete
tkstang Mar 20, 2026
fcf82bf
chore(oat): add discovery artifact for backlog-init-command
tkstang Mar 20, 2026
f0fa339
chore(oat): record final review artifact
tkstang Mar 20, 2026
ca02228
chore(oat): queue final review fixes for backlog-init-command
tkstang Mar 20, 2026
5748edd
fix(p03-t01): persist backlog scaffold directories in git
tkstang Mar 20, 2026
00cc684
chore(oat): update tracking artifacts for p03-t01
tkstang Mar 20, 2026
97e05b6
test(p03-t02): cover backlog init command surface
tkstang Mar 20, 2026
9b354cf
chore(oat): update tracking artifacts for p03-t02
tkstang Mar 20, 2026
d6ed334
chore(oat): record final review artifact
tkstang Mar 21, 2026
f422b2c
chore(oat): record passing final review for backlog-init-command
tkstang Mar 21, 2026
135945f
chore(backlog-init-command): prepare final PR
tkstang Mar 21, 2026
c25579b
docs(backlog-init-command): update documentation from project artifacts
tkstang Mar 21, 2026
be0a0d8
chore(backlog-init-command): mark docs updated
tkstang Mar 21, 2026
f730be5
chore(oat): complete project lifecycle for backlog-init-command
tkstang Mar 21, 2026
e13fb60
chore(oat): clarify project completion skill guidance
tkstang Mar 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions .agents/skills/oat-project-complete/SKILL.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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"
Expand All @@ -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.
Expand Down
3 changes: 2 additions & 1 deletion .oat/repo/reference/backlog/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`).

Expand All @@ -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 |
Expand Down
26 changes: 26 additions & 0 deletions .oat/repo/reference/backlog/items/project-complete-cli-helper.md
Original file line number Diff line number Diff line change
@@ -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.
5 changes: 3 additions & 2 deletions .oat/repo/reference/current-state.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.<name>.enabled`).
Expand Down Expand Up @@ -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 <filename>`
- `oat backlog regenerate-index`
3. Refresh repo references:
Expand Down
3 changes: 2 additions & 1 deletion apps/oat-docs/docs/guide/cli-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <filename>` - generate a unique backlog ID from a filename seed
- `oat backlog generate-id <filename> --created-at <timestamp>` - 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 ...`

Expand Down
2 changes: 2 additions & 0 deletions apps/oat-docs/docs/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
```

Expand Down
127 changes: 127 additions & 0 deletions packages/cli/src/commands/backlog/index.test.ts
Original file line number Diff line number Diff line change
@@ -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<typeof vi.fn>;
resolveProjectRoot: ReturnType<typeof vi.fn>;
} {
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<void> {
const program = new Command()
.name('oat')
.option('--json')
.option('--verbose')
.option('--cwd <path>')
.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);
});
});
35 changes: 35 additions & 0 deletions packages/cli/src/commands/backlog/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -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,
};

Expand Down Expand Up @@ -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 <path>',
'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')
Expand Down
Loading
Loading