@open-agent-toolkit/cli 0.0.32 → 0.0.33

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/README.md CHANGED
@@ -34,6 +34,7 @@ Additional useful entry points:
34
34
  - `oat config dump --json`
35
35
  - `oat project status --json`
36
36
  - `oat project list --json`
37
+ - `oat project complete-state /path/to/project`
37
38
  - `oat project archive sync`
38
39
  - `oat doctor`
39
40
 
@@ -44,6 +45,7 @@ Use these commands when you want structured runtime/project state out of the CLI
44
45
  - `oat config dump --json` - emit merged OAT config with per-key source attribution
45
46
  - `oat project status --json` - emit the active project's full parsed control-plane state
46
47
  - `oat project list --json` - emit summary state for tracked projects under the configured projects root
48
+ - `oat project complete-state <project-path>` - emit the canonical completed lifecycle shape into a tracked project's `state.md`
47
49
 
48
50
  ## Requirements
49
51
 
@@ -39,6 +39,7 @@ Notable inspection commands introduced in the current CLI surface:
39
39
  - `oat config dump --json` - merged config with source attribution
40
40
  - `oat project status --json` - full parsed state for the active tracked project
41
41
  - `oat project list --json` - summary state for tracked projects under the configured projects root
42
+ - `oat project complete-state <project-path>` - apply the canonical completed-state mutation to a project's `state.md`; used by `oat-project-complete` during lifecycle closeout
42
43
 
43
44
  ## `oat config` surface flags
44
45
 
@@ -1,6 +1,6 @@
1
1
  {
2
- "cli": "0.0.32",
3
- "docs-config": "0.0.32",
4
- "docs-theme": "0.0.32",
5
- "docs-transforms": "0.0.32"
2
+ "cli": "0.0.33",
3
+ "docs-config": "0.0.33",
4
+ "docs-theme": "0.0.33",
5
+ "docs-transforms": "0.0.33"
6
6
  }
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: oat-project-complete
3
- version: 1.4.0
3
+ version: 1.4.1
4
4
  description: Use when all implementation work is finished and the project is ready to close. Marks the OAT project lifecycle as complete.
5
5
  disable-model-invocation: true
6
6
  user-invocable: true
@@ -254,41 +254,19 @@ Rules:
254
254
 
255
255
  ### Step 5: Set Lifecycle Complete
256
256
 
257
- Update `state.md` frontmatter to add/update `oat_lifecycle: complete` and set completion timestamps:
257
+ Delegate the canonical `state.md` completion mutation to the CLI:
258
258
 
259
259
  ```bash
260
- STATE_FILE="${PROJECT_PATH}/state.md"
261
- NOW_UTC=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
262
-
263
- # Check if oat_lifecycle already exists
264
- if grep -q "^oat_lifecycle:" "$STATE_FILE"; then
265
- # Update existing (portable approach using temp file)
266
- sed 's/^oat_lifecycle:.*/oat_lifecycle: complete/' "$STATE_FILE" > "$STATE_FILE.tmp"
267
- mv "$STATE_FILE.tmp" "$STATE_FILE"
268
- else
269
- # Add after oat_phase_status line using awk (more portable for multi-line inserts)
270
- awk '/^oat_phase_status:/ {print; print "oat_lifecycle: complete"; next} 1' "$STATE_FILE" > "$STATE_FILE.tmp"
271
- mv "$STATE_FILE.tmp" "$STATE_FILE"
260
+ COMPLETE_STATE_ARGS=("$PROJECT_PATH")
261
+ if [[ "$SHOULD_ARCHIVE" == "true" && "$IS_SHARED_PROJECT" == "true" ]]; then
262
+ COMPLETE_STATE_ARGS+=("--archived")
272
263
  fi
273
264
 
274
- # Set completion and state-updated timestamps
275
- sed -E "s/^oat_project_completed:.*/oat_project_completed: \"$NOW_UTC\"/" "$STATE_FILE" > "$STATE_FILE.tmp"
276
- mv "$STATE_FILE.tmp" "$STATE_FILE"
277
- sed -E "s/^oat_project_state_updated:.*/oat_project_state_updated: \"$NOW_UTC\"/" "$STATE_FILE" > "$STATE_FILE.tmp"
278
- mv "$STATE_FILE.tmp" "$STATE_FILE"
265
+ oat project complete-state "${COMPLETE_STATE_ARGS[@]}"
279
266
  ```
280
267
 
281
- Then update the markdown body in `state.md` so the completion state is explicit and does not rely on reference lookups:
282
-
283
- - Set `**Status:** Complete`
284
- - Set `**Last Updated:**` to the completion date in `YYYY-MM-DD`
285
- - In `## Current Phase`, replace the body with:
286
- - `Lifecycle complete; archived locally` when the project is archived in Step 8
287
- - `Lifecycle complete` when the project is completed without archive
288
- - In `## Progress`, preserve the existing completed workflow/review bullets and add `- ✓ Project lifecycle complete` if it is not already present
289
- - In `## Next Milestone`, replace the body with `None. Project complete.`
290
-
291
- Do not infer these body mutations from other archived projects. Apply them directly as part of this skill.
268
+ The CLI command owns both the frontmatter completion fields and the canonical markdown body updates for `state.md`.
269
+ It must set `oat_lifecycle: complete`, completion timestamps, `**Status:** Complete`, `**Last Updated:**`, the canonical `## Current Phase` body, normalized `## Progress`, and `## Next Milestone`.
292
270
 
293
271
  ### Step 6: Clear Active Project Pointer
294
272
 
@@ -0,0 +1,16 @@
1
+ import { readFile as defaultReadFile, writeFile as defaultWriteFile } from 'node:fs/promises';
2
+ import { buildCommandContext, type CommandContext } from '../../../app/command-context.js';
3
+ import { dirExists, fileExists } from '../../../fs/io.js';
4
+ import { Command } from 'commander';
5
+ interface ProjectCompleteStateDependencies {
6
+ buildCommandContext: (options: Parameters<typeof buildCommandContext>[0]) => CommandContext;
7
+ resolveProjectRoot: (cwd: string) => Promise<string>;
8
+ readFile: typeof defaultReadFile;
9
+ writeFile: typeof defaultWriteFile;
10
+ dirExists: typeof dirExists;
11
+ fileExists: typeof fileExists;
12
+ now: () => Date;
13
+ }
14
+ export declare function createProjectCompleteStateCommand(overrides?: Partial<ProjectCompleteStateDependencies>): Command;
15
+ export {};
16
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/complete-state/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,QAAQ,IAAI,eAAe,EAC3B,SAAS,IAAI,gBAAgB,EAC9B,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EAAE,mBAAmB,EAAE,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAGhF,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAE/C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAQpC,UAAU,gCAAgC;IACxC,mBAAmB,EAAE,CACnB,OAAO,EAAE,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC,CAAC,CAAC,KAC/C,cAAc,CAAC;IACpB,kBAAkB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACrD,QAAQ,EAAE,OAAO,eAAe,CAAC;IACjC,SAAS,EAAE,OAAO,gBAAgB,CAAC;IACnC,SAAS,EAAE,OAAO,SAAS,CAAC;IAC5B,UAAU,EAAE,OAAO,UAAU,CAAC;IAC9B,GAAG,EAAE,MAAM,IAAI,CAAC;CACjB;AAsED,wBAAgB,iCAAiC,CAC/C,SAAS,GAAE,OAAO,CAAC,gCAAgC,CAAM,GACxD,OAAO,CA2BT"}
@@ -0,0 +1,78 @@
1
+ import { readFile as defaultReadFile, writeFile as defaultWriteFile, } from 'node:fs/promises';
2
+ import { isAbsolute, join } from 'node:path';
3
+ import { buildCommandContext } from '../../../app/command-context.js';
4
+ import { readGlobalOptions } from '../../shared/shared.utils.js';
5
+ import { CliError } from '../../../errors/cli-error.js';
6
+ import { dirExists, fileExists } from '../../../fs/io.js';
7
+ import { resolveProjectRoot } from '../../../fs/paths.js';
8
+ import { Command } from 'commander';
9
+ import { renderCompletedProjectState } from './state-utils.js';
10
+ const DEFAULT_DEPENDENCIES = {
11
+ buildCommandContext,
12
+ resolveProjectRoot,
13
+ readFile: defaultReadFile,
14
+ writeFile: defaultWriteFile,
15
+ dirExists,
16
+ fileExists,
17
+ now: () => new Date(),
18
+ };
19
+ function resolveTargetProjectPath(repoRoot, projectPath) {
20
+ return isAbsolute(projectPath) ? projectPath : join(repoRoot, projectPath);
21
+ }
22
+ async function runProjectCompleteState(projectPath, options, context, dependencies) {
23
+ try {
24
+ const repoRoot = await dependencies.resolveProjectRoot(context.cwd);
25
+ const targetProjectPath = resolveTargetProjectPath(repoRoot, projectPath);
26
+ if (!(await dependencies.dirExists(targetProjectPath))) {
27
+ throw new CliError(`Project not found: ${projectPath}`, 1);
28
+ }
29
+ const statePath = join(targetProjectPath, 'state.md');
30
+ if (!(await dependencies.fileExists(statePath))) {
31
+ throw new CliError(`Project state.md not found: ${statePath}`, 1);
32
+ }
33
+ const now = dependencies.now();
34
+ const content = await dependencies.readFile(statePath, 'utf8');
35
+ const updatedContent = renderCompletedProjectState(content, {
36
+ archived: options.archived ?? false,
37
+ nowUtc: now.toISOString(),
38
+ today: now.toISOString().slice(0, 10),
39
+ });
40
+ await dependencies.writeFile(statePath, updatedContent, 'utf8');
41
+ if (context.json) {
42
+ context.logger.json({
43
+ status: 'ok',
44
+ projectPath,
45
+ statePath,
46
+ archived: options.archived ?? false,
47
+ });
48
+ }
49
+ else {
50
+ context.logger.info(`Updated completed project state: ${projectPath}`);
51
+ }
52
+ process.exitCode = 0;
53
+ }
54
+ catch (error) {
55
+ const message = error instanceof Error ? error.message : String(error);
56
+ if (context.json) {
57
+ context.logger.json({ status: 'error', message });
58
+ }
59
+ else {
60
+ context.logger.error(message);
61
+ }
62
+ process.exitCode = error instanceof CliError ? error.exitCode : 1;
63
+ }
64
+ }
65
+ export function createProjectCompleteStateCommand(overrides = {}) {
66
+ const dependencies = {
67
+ ...DEFAULT_DEPENDENCIES,
68
+ ...overrides,
69
+ };
70
+ return new Command('complete-state')
71
+ .description('Update a project state.md to the completed lifecycle shape')
72
+ .argument('<project-path>', 'Project path to update')
73
+ .option('--archived', 'Mark the completed project as archived locally')
74
+ .action(async (projectPath, options, command) => {
75
+ const context = dependencies.buildCommandContext(readGlobalOptions(command));
76
+ await runProjectCompleteState(projectPath, options, context, dependencies);
77
+ });
78
+ }
@@ -0,0 +1,7 @@
1
+ export interface CompleteProjectStateOptions {
2
+ archived: boolean;
3
+ nowUtc: string;
4
+ today: string;
5
+ }
6
+ export declare function renderCompletedProjectState(content: string, options: CompleteProjectStateOptions): string;
7
+ //# sourceMappingURL=state-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state-utils.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/complete-state/state-utils.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf;AAoED,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,2BAA2B,GACnC,MAAM,CA8DR"}
@@ -0,0 +1,72 @@
1
+ import { getFrontmatterBlock, getFrontmatterField, } from '../../shared/frontmatter.js';
2
+ import { replaceFrontmatter, upsertFrontmatterField, } from '../../shared/frontmatter-write.js';
3
+ function replaceLine(content, pattern, nextLine) {
4
+ return pattern.test(content) ? content.replace(pattern, nextLine) : content;
5
+ }
6
+ function findSectionBounds(content, heading) {
7
+ const marker = `## ${heading}\n\n`;
8
+ const start = content.indexOf(marker);
9
+ if (start === -1) {
10
+ return null;
11
+ }
12
+ const bodyStart = start + marker.length;
13
+ const nextHeading = content.indexOf('\n## ', bodyStart);
14
+ return {
15
+ start,
16
+ bodyStart,
17
+ end: nextHeading === -1 ? content.length : nextHeading,
18
+ };
19
+ }
20
+ function readSectionBody(content, heading) {
21
+ const bounds = findSectionBounds(content, heading);
22
+ if (!bounds) {
23
+ return '';
24
+ }
25
+ return content.slice(bounds.bodyStart, bounds.end).trim();
26
+ }
27
+ function replaceSection(content, heading, body) {
28
+ const bounds = findSectionBounds(content, heading);
29
+ if (!bounds) {
30
+ return content;
31
+ }
32
+ return [
33
+ content.slice(0, bounds.start),
34
+ `## ${heading}\n\n${body.trim()}\n`,
35
+ content.slice(bounds.end),
36
+ ].join('');
37
+ }
38
+ function renderCompletedProgress(content) {
39
+ const existingLines = readSectionBody(content, 'Progress')
40
+ .split('\n')
41
+ .map((line) => line.trim())
42
+ .filter((line) => line.length > 0 && line.startsWith('- ✓'));
43
+ if (!existingLines.includes('- ✓ Project lifecycle complete')) {
44
+ existingLines.push('- ✓ Project lifecycle complete');
45
+ }
46
+ return existingLines.join('\n');
47
+ }
48
+ export function renderCompletedProjectState(content, options) {
49
+ const frontmatter = getFrontmatterBlock(content);
50
+ if (!frontmatter) {
51
+ throw new Error('state.md is missing frontmatter');
52
+ }
53
+ let nextBlock = upsertFrontmatterField(frontmatter, 'oat_lifecycle', 'complete', true).nextBlock;
54
+ nextBlock = upsertFrontmatterField(nextBlock, 'oat_project_completed', `"${options.nowUtc}"`, true).nextBlock;
55
+ nextBlock = upsertFrontmatterField(nextBlock, 'oat_project_state_updated', `"${options.nowUtc}"`, true).nextBlock;
56
+ let nextContent = nextBlock === frontmatter
57
+ ? content
58
+ : replaceFrontmatter(content, nextBlock);
59
+ nextContent = replaceLine(nextContent, /^\*\*Status:\*\*.*$/m, '**Status:** Complete');
60
+ nextContent = replaceLine(nextContent, /^\*\*Last Updated:\*\*.*$/m, `**Last Updated:** ${options.today}`);
61
+ const currentPhase = options.archived
62
+ ? 'Lifecycle complete; archived locally'
63
+ : 'Lifecycle complete';
64
+ nextContent = replaceSection(nextContent, 'Current Phase', currentPhase);
65
+ nextContent = replaceSection(nextContent, 'Progress', renderCompletedProgress(nextContent));
66
+ nextContent = replaceSection(nextContent, 'Next Milestone', 'None. Project complete.');
67
+ const currentLifecycle = getFrontmatterField(nextBlock, 'oat_lifecycle');
68
+ if (currentLifecycle !== 'complete') {
69
+ throw new Error('Failed to set oat_lifecycle: complete');
70
+ }
71
+ return nextContent;
72
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/project/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAUpC,wBAAgB,oBAAoB,IAAI,OAAO,CAU9C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/project/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAWpC,wBAAgB,oBAAoB,IAAI,OAAO,CAW9C"}
@@ -1,5 +1,6 @@
1
1
  import { Command } from 'commander';
2
2
  import { createProjectArchiveCommand } from './archive/index.js';
3
+ import { createProjectCompleteStateCommand } from './complete-state/index.js';
3
4
  import { createProjectListCommand } from './list.js';
4
5
  import { createProjectNewCommand } from './new/index.js';
5
6
  import { createProjectOpenCommand } from './open/index.js';
@@ -10,6 +11,7 @@ export function createProjectCommand() {
10
11
  return new Command('project')
11
12
  .description('Manage OAT project workflows')
12
13
  .addCommand(createProjectArchiveCommand())
14
+ .addCommand(createProjectCompleteStateCommand())
13
15
  .addCommand(createProjectListCommand())
14
16
  .addCommand(createProjectNewCommand())
15
17
  .addCommand(createProjectOpenCommand())
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-agent-toolkit/cli",
3
- "version": "0.0.32",
3
+ "version": "0.0.33",
4
4
  "private": false,
5
5
  "description": "Open Agent Toolkit CLI",
6
6
  "homepage": "https://github.com/voxmedia/open-agent-toolkit/tree/main/packages/cli",
@@ -33,7 +33,7 @@
33
33
  "ora": "^9.0.0",
34
34
  "yaml": "2.8.2",
35
35
  "zod": "^3.25.76",
36
- "@open-agent-toolkit/control-plane": "0.0.32"
36
+ "@open-agent-toolkit/control-plane": "0.0.33"
37
37
  },
38
38
  "devDependencies": {
39
39
  "@types/node": "^22.10.0",