@open-agent-toolkit/cli 0.1.24 → 0.1.26

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.
@@ -58,7 +58,7 @@ Notable commands introduced in the current CLI surface:
58
58
  - `oat project status --field <path>` - print one arbitrary dot-path field from the same status payload, e.g. `project.workflowMode` or `project.timestamps.stateUpdated`. Missing/null fields print `null`; object and array fields print compact JSON.
59
59
  - `oat project status --project-path <path>` - read from a repo-relative or absolute project path instead of `.oat/config.local.json`'s active project pointer. Combine it with `--field` or `--shell` when a skill has already resolved the target project path.
60
60
  - `oat project status --shell NAME=path ...` - print shell-safe assignments for one or more fields from one status read, e.g. `WORKFLOW_MODE='quick'`. This is the preferred multi-field read API for skills. See [Writing Skills → Reading project state](../contributing/skills.md#reading-project-state) for examples and the `npx`-backed `oat` shim contract.
61
- - `oat review latest --json` - find the newest review artifact by `oat_generated_at`, scanning the active or specified project's `reviews/` and `reviews/archived/` directories plus ad-hoc review locations. Same-time candidates use target priority, then lifecycle recency (`final` > higher phase/task > lower phase/task). The JSON contract returns `path`, `scope`, `generatedAt`, and `kind` (`project` or `adhoc`), with `null` values when no review exists.
61
+ - `oat review latest --json` - find the newest review artifact by `oat_generated_at`, scanning the active or specified project's `reviews/` and `reviews/archived/` directories plus ad-hoc review locations. Same-time candidates use target priority, then lifecycle recency (`final` > higher phase/task > lower phase/task). The JSON contract returns `path`, `scope`, `generatedAt`, `kind` (`project` or `adhoc`), `archived`, and `actionable`, with `null` values when no review exists. Archived project reviews remain discoverable as history but return `actionable: false`.
62
62
  - `oat project list --json` - summary state for tracked projects under the configured projects root
63
63
  - `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
64
64
  - `oat project archive [project-path]` - archive a tracked project through the same local move, summary export, and optional S3 upload path used by completion. When omitted, the project path falls back to the active project.
@@ -28,7 +28,7 @@ oat review latest --json
28
28
  oat review latest --project .oat/projects/shared/example --json
29
29
  ```
30
30
 
31
- The resolver orders candidates by `oat_generated_at` frontmatter, not filesystem mtime. With an active or explicit project, it scans the project's `reviews/` directory first, then `reviews/archived/`, then ad-hoc review locations (`.oat/repo/reviews/` and `.oat/projects/local/orphan-reviews/`). When candidates share the same generated time, active project reviews outrank archived and ad-hoc reviews, then lifecycle recency breaks remaining ties (`final` > higher phase/task scope > lower phase/task scope). The JSON response contains `path`, `scope`, `generatedAt`, and `kind` (`project` or `adhoc`). If no review exists, those fields are `null`.
31
+ The resolver orders candidates by `oat_generated_at` frontmatter, not filesystem mtime. With an active or explicit project, it scans the project's `reviews/` directory first, then `reviews/archived/`, then ad-hoc review locations (`.oat/repo/reviews/` and `.oat/projects/local/orphan-reviews/`). When candidates share the same generated time, active project reviews outrank archived and ad-hoc reviews, then lifecycle recency breaks remaining ties (`final` > higher phase/task scope > lower phase/task scope). The JSON response contains `path`, `scope`, `generatedAt`, `kind` (`project` or `adhoc`), `archived`, and `actionable`. Archived project reviews remain discoverable as history but return `actionable: false`; active top-level project reviews return `actionable: true`. If no review exists, those fields are `null`.
32
32
 
33
33
  `oat-project-review-receive` uses this resolver when it is invoked from natural language and needs to offer the latest project review, or route an ad-hoc result to `oat-review-receive`.
34
34
 
@@ -1,6 +1,6 @@
1
1
  {
2
- "cli": "0.1.24",
3
- "docs-config": "0.1.24",
4
- "docs-theme": "0.1.24",
5
- "docs-transforms": "0.1.24"
2
+ "cli": "0.1.26",
3
+ "docs-config": "0.1.26",
4
+ "docs-theme": "0.1.26",
5
+ "docs-transforms": "0.1.26"
6
6
  }
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: oat-project-next
3
- version: 1.0.4
3
+ version: 1.0.5
4
4
  description: Use when continuing work on the active OAT project. Reads project state, determines the next lifecycle action, and invokes the appropriate skill automatically.
5
5
  disable-model-invocation: true
6
6
  user-invocable: true
@@ -283,9 +283,9 @@ Parse plan.md Reviews table for a row with `Scope="final"` and `Type="code"`:
283
283
  → Route to `oat-project-review-provide` (with scope hint: "code final")
284
284
  → Announce: "Review fixes implemented — triggering re-review"
285
285
 
286
- - If row exists with other non-passed status (e.g., `"received"`, `"fixes_added"`):
287
- → Route to `oat-project-review-receive` (review findings not yet converted to tasks)
288
- → Announce: "Final review in progresscontinuing review cycle"
286
+ - If row exists with other non-passed status (e.g., `"received"`, `"fixes_added"`) and Step 5.2 did not find an active top-level review artifact:
287
+ → Route to `oat-project-review-provide` (with scope hint: "code final")
288
+ → Announce: "Final review is not passed and no active review artifact exists rerunning final review"
289
289
 
290
290
  - If row exists with `Status="passed"`:
291
291
  → Continue to 5.4
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: oat-project-review-receive
3
- version: 1.5.2
3
+ version: 1.5.3
4
4
  description: Use when the user explicitly asks to receive review findings for an OAT project — e.g. "receive review", "process review", "process the project review", or confirms a previously offered review-receive step. Do NOT auto-invoke merely because a review file exists. Resolves the latest review and offers before acting.
5
5
  disable-model-invocation: false
6
6
  user-invocable: true
@@ -105,7 +105,8 @@ Selection rules:
105
105
  - Use `oat review latest` as the first-choice resolver. It scans project reviews (`reviews/` and `reviews/archived/`) plus ad-hoc review locations and orders candidates by `oat_generated_at` frontmatter rather than filesystem mtime.
106
106
  - Read the JSON result:
107
107
  - `path: null` means no review target was found.
108
- - `kind: "project"` means this skill can process the target when the path is an active top-level project review.
108
+ - `kind: "project"` and `actionable: true` means this skill can process the target when the path is an active top-level project review.
109
+ - `archived: true` or `actionable: false` means the target is historical/non-actionable for project review-receive.
109
110
  - `kind: "adhoc"` means route to `oat-review-receive` after offering that handoff to the user.
110
111
  - Only process active project review artifacts in the top level of `"$PROJECT_PATH/reviews/"`.
111
112
  - Treat archived project artifacts as history only; do not receive them automatically. If `oat review latest` returns an archived project review, tell the user no active project review is waiting and offer to run `oat-project-review-provide`.
@@ -1 +1 @@
1
- {"version":3,"file":"expand.d.ts","sourceRoot":"","sources":["../../../src/commands/local/expand.ts"],"names":[],"mappings":"AAuEA,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEnD;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAEzE;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,EAAE,GACnB,OAAO,CAAC,YAAY,CAAC,CA0BvB"}
1
+ {"version":3,"file":"expand.d.ts","sourceRoot":"","sources":["../../../src/commands/local/expand.ts"],"names":[],"mappings":"AAqHA,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEnD;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAEzE;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,EAAE,GACnB,OAAO,CAAC,YAAY,CAAC,CA4BvB"}
@@ -1,6 +1,31 @@
1
1
  import { readdir } from 'node:fs/promises';
2
2
  import { join } from 'node:path';
3
3
  const GLOB_CHARS = /[*?[]/;
4
+ const PRUNED_CANDIDATE_DIRECTORIES = new Set([
5
+ '.cache',
6
+ '.git',
7
+ '.gradle',
8
+ '.next',
9
+ '.nuxt',
10
+ '.output',
11
+ '.parcel-cache',
12
+ '.pnpm-store',
13
+ '.pytest_cache',
14
+ '.ruff_cache',
15
+ '.turbo',
16
+ '.venv',
17
+ '.vite',
18
+ '.worktrees',
19
+ '.yarn',
20
+ '__pycache__',
21
+ 'build',
22
+ 'coverage',
23
+ 'dist',
24
+ 'node_modules',
25
+ 'out',
26
+ 'target',
27
+ 'tmp',
28
+ ]);
4
29
  function normalizePattern(path) {
5
30
  return path.replaceAll('\\', '/').replace(/^\.\//, '').replace(/\/+$/, '');
6
31
  }
@@ -40,15 +65,30 @@ function globToRegExp(pattern) {
40
65
  regex += '$';
41
66
  return new RegExp(regex);
42
67
  }
68
+ function isOatStatePath(path) {
69
+ return path === '.oat' || path.startsWith('.oat/');
70
+ }
71
+ function shouldPruneCandidateDirectory(path, name) {
72
+ return !isOatStatePath(path) && PRUNED_CANDIDATE_DIRECTORIES.has(name);
73
+ }
43
74
  async function collectRelativePaths(root, current = '') {
44
- const dirPath = current === '' ? root : join(root, current);
45
- const entries = await readdir(dirPath, { withFileTypes: true });
46
75
  const paths = [];
47
- for (const entry of entries) {
48
- const relativePath = current === '' ? entry.name : `${current}/${entry.name}`;
49
- paths.push(relativePath);
50
- if (entry.isDirectory()) {
51
- paths.push(...(await collectRelativePaths(root, relativePath)));
76
+ const pendingDirectories = [current];
77
+ while (pendingDirectories.length > 0) {
78
+ const directory = pendingDirectories.pop();
79
+ const dirPath = directory === '' ? root : join(root, directory);
80
+ const entries = await readdir(dirPath, { withFileTypes: true });
81
+ for (const entry of entries) {
82
+ const relativePath = directory === '' ? entry.name : `${directory}/${entry.name}`;
83
+ const shouldPrune = entry.isDirectory() &&
84
+ shouldPruneCandidateDirectory(relativePath, entry.name);
85
+ if (shouldPrune) {
86
+ continue;
87
+ }
88
+ paths.push(relativePath);
89
+ if (entry.isDirectory()) {
90
+ pendingDirectories.push(relativePath);
91
+ }
52
92
  }
53
93
  }
54
94
  return paths;
@@ -76,7 +116,9 @@ export async function expandLocalPaths(root, localPaths) {
76
116
  missingGlobs.push(localPath);
77
117
  }
78
118
  else {
79
- resolved.push(...matches.sort());
119
+ for (const match of matches.sort()) {
120
+ resolved.push(match);
121
+ }
80
122
  }
81
123
  }
82
124
  return { resolved, missingGlobs };
@@ -7,6 +7,8 @@ export interface LatestReview {
7
7
  scope: string;
8
8
  generatedAt: string;
9
9
  kind: ReviewLatestKind;
10
+ archived: boolean;
11
+ actionable: boolean;
10
12
  }
11
13
  export interface FindLatestReviewOptions {
12
14
  repoRoot: string;
@@ -1 +1 @@
1
- {"version":3,"file":"latest.d.ts","sourceRoot":"","sources":["../../../src/commands/review/latest.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAM9B,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAE7E,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,OAAO,CAAC;AAEnD,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,gBAAgB,CAAC;CACxB;AAQD,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,UAAU,wBAAwB;IAChC,mBAAmB,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,cAAc,CAAC;IAChE,kBAAkB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACrD,kBAAkB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;CACnE;AA6HD,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,uBAAuB,GAC/B,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAmE9B;AA+CD,wBAAgB,yBAAyB,CACvC,SAAS,GAAE,OAAO,CAAC,wBAAwB,CAAM,GAChD,OAAO,CAkBT"}
1
+ {"version":3,"file":"latest.d.ts","sourceRoot":"","sources":["../../../src/commands/review/latest.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAM9B,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAE7E,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,OAAO,CAAC;AAEnD,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,gBAAgB,CAAC;IACvB,QAAQ,EAAE,OAAO,CAAC;IAClB,UAAU,EAAE,OAAO,CAAC;CACrB;AAQD,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,UAAU,wBAAwB;IAChC,mBAAmB,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,cAAc,CAAC;IAChE,kBAAkB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACrD,kBAAkB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;CACnE;AAmID,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,uBAAuB,GAC/B,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAqF9B;AA+CD,wBAAgB,yBAAyB,CACvC,SAAS,GAAE,OAAO,CAAC,wBAAwB,CAAM,GAChD,OAAO,CAkBT"}
@@ -16,6 +16,8 @@ const EMPTY_RESULT = {
16
16
  scope: null,
17
17
  generatedAt: null,
18
18
  kind: null,
19
+ archived: null,
20
+ actionable: null,
19
21
  };
20
22
  function normalizeRepoRelativePath(repoRoot, pathValue) {
21
23
  const rawPath = pathValue.trim();
@@ -60,7 +62,7 @@ async function listMarkdownFiles(repoRoot, relativeDir) {
60
62
  throw error;
61
63
  }
62
64
  }
63
- async function readReviewCandidate(repoRoot, relativePath, kind, priority) {
65
+ async function readReviewCandidate(repoRoot, relativePath, kind, priority, archived, actionable) {
64
66
  const content = await readFile(join(repoRoot, relativePath), 'utf8');
65
67
  const frontmatter = getFrontmatterBlock(content);
66
68
  if (!frontmatter) {
@@ -80,6 +82,8 @@ async function readReviewCandidate(repoRoot, relativePath, kind, priority) {
80
82
  scope,
81
83
  generatedAt,
82
84
  kind,
85
+ archived,
86
+ actionable,
83
87
  generatedTime,
84
88
  lifecycleRank: reviewLifecycleRank(scope),
85
89
  priority,
@@ -107,20 +111,32 @@ export async function findLatestReview(options) {
107
111
  dir: `${projectPath}/reviews`,
108
112
  kind: 'project',
109
113
  priority: 0,
114
+ archived: false,
115
+ actionable: true,
110
116
  }, {
111
117
  dir: `${projectPath}/reviews/archived`,
112
118
  kind: 'project',
113
119
  priority: 1,
120
+ archived: true,
121
+ actionable: false,
114
122
  });
115
123
  }
116
- scanTargets.push({ dir: '.oat/repo/reviews', kind: 'adhoc', priority: 2 }, {
124
+ scanTargets.push({
125
+ dir: '.oat/repo/reviews',
126
+ kind: 'adhoc',
127
+ priority: 2,
128
+ archived: false,
129
+ actionable: true,
130
+ }, {
117
131
  dir: '.oat/projects/local/orphan-reviews',
118
132
  kind: 'adhoc',
119
133
  priority: 3,
134
+ archived: false,
135
+ actionable: true,
120
136
  });
121
137
  const candidates = (await Promise.all(scanTargets.map(async (target) => {
122
138
  const files = await listMarkdownFiles(options.repoRoot, target.dir);
123
- return Promise.all(files.map((file) => readReviewCandidate(options.repoRoot, file, target.kind, target.priority)));
139
+ return Promise.all(files.map((file) => readReviewCandidate(options.repoRoot, file, target.kind, target.priority, target.archived, target.actionable)));
124
140
  })))
125
141
  .flat()
126
142
  .filter((candidate) => candidate !== null)
@@ -134,6 +150,8 @@ export async function findLatestReview(options) {
134
150
  scope: latest.scope,
135
151
  generatedAt: latest.generatedAt,
136
152
  kind: latest.kind,
153
+ archived: latest.archived,
154
+ actionable: latest.actionable,
137
155
  };
138
156
  }
139
157
  async function resolveProjectPath(repoRoot, explicitProject, dependencies) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-agent-toolkit/cli",
3
- "version": "0.1.24",
3
+ "version": "0.1.26",
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",
@@ -34,7 +34,7 @@
34
34
  "ora": "^9.0.0",
35
35
  "yaml": "2.8.2",
36
36
  "zod": "^3.25.76",
37
- "@open-agent-toolkit/control-plane": "0.1.24"
37
+ "@open-agent-toolkit/control-plane": "0.1.26"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@types/node": "^22.10.0",