@open-agent-toolkit/cli 0.2.50 → 0.2.51

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.
Files changed (60) hide show
  1. package/assets/bundle-metadata.json +1 -1
  2. package/assets/docs/reference/cli-reference.md +20 -5
  3. package/assets/docs/reference/file-locations.md +8 -2
  4. package/assets/docs/reference/oat-directory-structure.md +14 -5
  5. package/assets/docs/workflows/projects/artifacts.md +9 -4
  6. package/assets/docs/workflows/projects/lifecycle.md +17 -5
  7. package/assets/docs/workflows/projects/picking-up-projects.md +26 -9
  8. package/assets/docs/workflows/projects/pr-flow.md +5 -3
  9. package/assets/docs/workflows/projects/reviewing-oat-prs.md +12 -8
  10. package/assets/public-package-versions.json +4 -4
  11. package/assets/skills/oat-project-autonomous/references/gate-inventory.md +2 -2
  12. package/assets/skills/oat-project-complete/SKILL.md +193 -26
  13. package/assets/skills/oat-project-complete/scripts/execute-synced-archive-entry.mjs +306 -0
  14. package/assets/skills/oat-project-complete/scripts/finalize-synced-archive.mjs +125 -0
  15. package/assets/skills/oat-project-complete/scripts/parse-synced-archive-resume-fields.mjs +106 -0
  16. package/assets/skills/oat-project-complete/scripts/recover-completion-receipts.mjs +110 -0
  17. package/assets/skills/oat-project-complete/scripts/resolve-synced-archive-entry.mjs +164 -0
  18. package/assets/skills/oat-project-document/references/docs/autonomy-contract.md +2 -2
  19. package/assets/skills/oat-project-implement/references/docs/autonomy-contract.md +2 -2
  20. package/assets/skills/oat-project-pr-final/references/docs/autonomy-contract.md +2 -2
  21. package/assets/skills/oat-project-quick-start/references/docs/autonomy-contract.md +2 -2
  22. package/dist/commands/project/archive/archive-utils.d.ts +8 -1
  23. package/dist/commands/project/archive/archive-utils.d.ts.map +1 -1
  24. package/dist/commands/project/archive/archive-utils.js +147 -50
  25. package/dist/commands/project/archive/push-runner.d.ts.map +1 -1
  26. package/dist/commands/project/archive/push-runner.js +15 -2
  27. package/dist/commands/project/links/compute.d.ts +1 -0
  28. package/dist/commands/project/links/compute.d.ts.map +1 -1
  29. package/dist/commands/project/links/compute.js +5 -4
  30. package/dist/commands/project/links/index.d.ts +9 -0
  31. package/dist/commands/project/links/index.d.ts.map +1 -1
  32. package/dist/commands/project/links/index.js +71 -2
  33. package/dist/commands/project/list.d.ts +10 -2
  34. package/dist/commands/project/list.d.ts.map +1 -1
  35. package/dist/commands/project/list.js +205 -29
  36. package/dist/commands/project/open/index.d.ts +2 -0
  37. package/dist/commands/project/open/index.d.ts.map +1 -1
  38. package/dist/commands/project/open/index.js +16 -1
  39. package/dist/commands/project/prune/index.d.ts +10 -4
  40. package/dist/commands/project/prune/index.d.ts.map +1 -1
  41. package/dist/commands/project/prune/index.js +85 -15
  42. package/dist/commands/project/pull/index.d.ts +4 -0
  43. package/dist/commands/project/pull/index.d.ts.map +1 -1
  44. package/dist/commands/project/pull/index.js +82 -3
  45. package/dist/commands/project/sync/git.d.ts +9 -0
  46. package/dist/commands/project/sync/git.d.ts.map +1 -1
  47. package/dist/commands/project/sync/git.js +18 -0
  48. package/dist/commands/project/sync/ref-sync.d.ts +16 -1
  49. package/dist/commands/project/sync/ref-sync.d.ts.map +1 -1
  50. package/dist/commands/project/sync/ref-sync.js +288 -17
  51. package/dist/commands/project/sync/resolve-target.d.ts +10 -0
  52. package/dist/commands/project/sync/resolve-target.d.ts.map +1 -1
  53. package/dist/commands/project/sync/resolve-target.js +56 -1
  54. package/dist/commands/shared/project-scope.d.ts +2 -0
  55. package/dist/commands/shared/project-scope.d.ts.map +1 -1
  56. package/dist/commands/shared/project-scope.js +5 -0
  57. package/dist/commands/state/generate.d.ts +2 -0
  58. package/dist/commands/state/generate.d.ts.map +1 -1
  59. package/dist/commands/state/generate.js +93 -28
  60. package/package.json +2 -2
@@ -2,10 +2,65 @@ import { realpath, stat } from 'node:fs/promises';
2
2
  import { isAbsolute, relative, resolve, sep } from 'node:path';
3
3
  import { defaultGitRunner } from '../../project/sync/git.js';
4
4
  import { resolveProjectsRoot } from '../../shared/oat-paths.js';
5
- import { assertValidProjectSlug, resolveProjectScope, resolveScopeRoot, syncedRecordPath, } from '../../shared/project-scope.js';
5
+ import { assertValidProjectSlug, completedSyncedRefName, resolveProjectScope, resolveScopeRoot, syncedRecordPath, } from '../../shared/project-scope.js';
6
6
  import { readOatLocalConfig } from '../../../config/oat-config.js';
7
7
  import { CliError } from '../../../errors/cli-error.js';
8
8
  import { assertCanonicalSyncTargetIdentity, buildSyncTarget, classifyRemoteRefLookup, classifyAdoptionRecord, } from './ref-sync.js';
9
+ async function remoteTerminalRefShas(target, activeRef, completedRef, git) {
10
+ const result = await git.run(['ls-remote', '--exit-code', target.remote, activeRef, completedRef], { cwd: target.repoRoot, allowFailure: true });
11
+ if (classifyRemoteRefLookup(result, target.remote, `${activeRef} and ${completedRef}`) === 'absent') {
12
+ return { activeSha: null, completedSha: null };
13
+ }
14
+ const requestedRefs = new Set([activeRef, completedRef]);
15
+ const advertised = new Map();
16
+ const rows = result.stdout.split('\n').filter((row) => row.length > 0);
17
+ if (rows.length === 0) {
18
+ throw new CliError(`Unable to verify terminal refs for ${target.slug}: git ls-remote returned a malformed empty advertisement.`, 2);
19
+ }
20
+ for (const row of rows) {
21
+ const fields = row.trim().split(/\s+/);
22
+ if (fields.length !== 2 ||
23
+ !/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/i.test(fields[0] ?? '')) {
24
+ throw new CliError(`Unable to verify terminal refs for ${target.slug}: git ls-remote returned a malformed row.`, 2);
25
+ }
26
+ const [sha, ref] = fields;
27
+ if (!requestedRefs.has(ref)) {
28
+ throw new CliError(`Unable to verify terminal refs for ${target.slug}: git ls-remote returned unexpected ref ${ref}.`, 2);
29
+ }
30
+ if (advertised.has(ref)) {
31
+ throw new CliError(`Unable to verify terminal refs for ${target.slug}: git ls-remote returned duplicate rows for ${ref}.`, 2);
32
+ }
33
+ advertised.set(ref, sha);
34
+ }
35
+ return {
36
+ activeSha: advertised.get(activeRef) ?? null,
37
+ completedSha: advertised.get(completedRef) ?? null,
38
+ };
39
+ }
40
+ export async function probeSyncedTerminalRefs(target, expectedSha, git) {
41
+ const activeRef = target.ref;
42
+ const completedRef = completedSyncedRefName(target.slug);
43
+ const { activeSha, completedSha } = await remoteTerminalRefShas(target, activeRef, completedRef, git);
44
+ const wrongSha = (activeSha !== null && activeSha !== expectedSha) ||
45
+ (completedSha !== null && completedSha !== expectedSha);
46
+ const state = wrongSha
47
+ ? 'wrong-sha'
48
+ : activeSha !== null && completedSha !== null
49
+ ? 'both'
50
+ : activeSha !== null
51
+ ? 'active-only'
52
+ : completedSha !== null
53
+ ? 'completed-only'
54
+ : 'absent';
55
+ return {
56
+ state,
57
+ activeRef,
58
+ completedRef,
59
+ expectedSha,
60
+ activeSha,
61
+ completedSha,
62
+ };
63
+ }
9
64
  async function pathExists(path) {
10
65
  try {
11
66
  await stat(path);
@@ -1,6 +1,7 @@
1
1
  export type ProjectScope = 'shared' | 'local' | 'synced';
2
2
  export declare const PROJECT_SCOPES: readonly ["shared", "local", "synced"];
3
3
  export declare const SYNCED_REF_NAMESPACE = "refs/oat/projects";
4
+ export declare const COMPLETED_SYNCED_REF_NAMESPACE = "refs/oat/completed";
4
5
  export declare const SYNCED_REMOTE = "origin";
5
6
  export declare function canonicalizePath(path: string): string;
6
7
  interface EffectiveConfigResult {
@@ -16,6 +17,7 @@ export declare function resolveScopeRoot(repoRoot: string, projectsRoot: string,
16
17
  export declare function resolveProjectScope(projectPath: string, projectsRoot: string, repoRoot: string): ProjectScope | null;
17
18
  export declare function assertValidProjectSlug(slug: string, exitCode?: 1 | 2): void;
18
19
  export declare function syncedRefName(slug: string): string;
20
+ export declare function completedSyncedRefName(slug: string): string;
19
21
  export declare function syncedRecordPath(scopeRoot: string, slug: string): string;
20
22
  export declare function isSyncedCheckout(projectPath: string): Promise<boolean>;
21
23
  export declare function resolveDefaultScope(repoRoot: string, env: NodeJS.ProcessEnv, dependencies?: Partial<ProjectScopeDependencies>): Promise<ProjectScope>;
@@ -1 +1 @@
1
- {"version":3,"file":"project-scope.d.ts","sourceRoot":"","sources":["../../../src/commands/shared/project-scope.ts"],"names":[],"mappings":"AAgBA,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAAC;AAEzD,eAAO,MAAM,cAAc,wCAIiB,CAAC;AAC7C,eAAO,MAAM,oBAAoB,sBAAsB,CAAC;AACxD,eAAO,MAAM,aAAa,WAAW,CAAC;AAItC,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAoBrD;AAED,UAAU,qBAAqB;IAC7B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CAC9C;AAED,UAAU,wBAAwB;IAChC,sBAAsB,EAAE,CACtB,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,EACrB,GAAG,EAAE,MAAM,CAAC,UAAU,KACnB,OAAO,CAAC,qBAAqB,CAAC,CAAC;CACrC;AAMD,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,GACnB,MAAM,CAKR;AAED,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,YAAY,GAClB,MAAM,CAOR;AAED,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,GACf,YAAY,GAAG,IAAI,CAmBrB;AAED,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,MAAM,EACZ,QAAQ,GAAE,CAAC,GAAG,CAAK,GAClB,IAAI,CAON;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGlD;AAED,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAGxE;AAED,wBAAsB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAa5E;AAeD,wBAAsB,mBAAmB,CACvC,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,CAAC,UAAU,EACtB,YAAY,GAAE,OAAO,CAAC,wBAAwB,CAAM,GACnD,OAAO,CAAC,YAAY,CAAC,CAqBvB"}
1
+ {"version":3,"file":"project-scope.d.ts","sourceRoot":"","sources":["../../../src/commands/shared/project-scope.ts"],"names":[],"mappings":"AAgBA,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAAC;AAEzD,eAAO,MAAM,cAAc,wCAIiB,CAAC;AAC7C,eAAO,MAAM,oBAAoB,sBAAsB,CAAC;AACxD,eAAO,MAAM,8BAA8B,uBAAuB,CAAC;AACnE,eAAO,MAAM,aAAa,WAAW,CAAC;AAItC,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAoBrD;AAED,UAAU,qBAAqB;IAC7B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CAC9C;AAED,UAAU,wBAAwB;IAChC,sBAAsB,EAAE,CACtB,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,EACrB,GAAG,EAAE,MAAM,CAAC,UAAU,KACnB,OAAO,CAAC,qBAAqB,CAAC,CAAC;CACrC;AAMD,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,GACnB,MAAM,CAKR;AAED,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,YAAY,GAClB,MAAM,CAOR;AAED,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,GACf,YAAY,GAAG,IAAI,CAmBrB;AAED,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,MAAM,EACZ,QAAQ,GAAE,CAAC,GAAG,CAAK,GAClB,IAAI,CAON;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGlD;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAG3D;AAED,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAGxE;AAED,wBAAsB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAa5E;AAeD,wBAAsB,mBAAmB,CACvC,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,CAAC,UAAU,EACtB,YAAY,GAAE,OAAO,CAAC,wBAAwB,CAAM,GACnD,OAAO,CAAC,YAAY,CAAC,CAqBvB"}
@@ -10,6 +10,7 @@ export const PROJECT_SCOPES = [
10
10
  'synced',
11
11
  ];
12
12
  export const SYNCED_REF_NAMESPACE = 'refs/oat/projects';
13
+ export const COMPLETED_SYNCED_REF_NAMESPACE = 'refs/oat/completed';
13
14
  export const SYNCED_REMOTE = 'origin';
14
15
  const PROJECT_SLUG_PATTERN = /^[a-zA-Z0-9_-]+$/;
15
16
  export function canonicalizePath(path) {
@@ -69,6 +70,10 @@ export function syncedRefName(slug) {
69
70
  assertValidProjectSlug(slug);
70
71
  return `${SYNCED_REF_NAMESPACE}/${slug}`;
71
72
  }
73
+ export function completedSyncedRefName(slug) {
74
+ assertValidProjectSlug(slug);
75
+ return `${COMPLETED_SYNCED_REF_NAMESPACE}/${slug}`;
76
+ }
72
77
  export function syncedRecordPath(scopeRoot, slug) {
73
78
  assertValidProjectSlug(slug);
74
79
  return join(scopeRoot, `${slug}.json`);
@@ -1,3 +1,4 @@
1
+ import { type GitRunner } from '../project/sync/git.js';
1
2
  export interface GitOperations {
2
3
  isGitRepo(root: string): boolean;
3
4
  diffFileCount(root: string, sha: string): number;
@@ -7,6 +8,7 @@ export interface GenerateStateOptions {
7
8
  env?: NodeJS.ProcessEnv;
8
9
  today?: string;
9
10
  git?: GitOperations;
11
+ gitRunner?: GitRunner;
10
12
  }
11
13
  export interface GenerateStateResult {
12
14
  dashboardPath: string;
@@ -1 +1 @@
1
- {"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../../src/commands/state/generate.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,aAAa;IAC5B,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IACjC,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;CAClD;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,aAAa,CAAC;CACrB;AAED,MAAM,WAAW,mBAAmB;IAClC,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAiHD,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAEvE;AAydD,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,mBAAmB,CAAC,CA2C9B"}
1
+ {"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../../src/commands/state/generate.ts"],"names":[],"mappings":"AAQA,OAAO,EAAoB,KAAK,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAU9E,MAAM,WAAW,aAAa;IAC5B,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IACjC,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;CAClD;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,aAAa,CAAC;IACpB,SAAS,CAAC,EAAE,SAAS,CAAC;CACvB;AAED,MAAM,WAAW,mBAAmB;IAClC,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAiHD,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAEvE;AAsgBD,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,mBAAmB,CAAC,CAkF9B"}
@@ -1,8 +1,14 @@
1
1
  import { execSync } from 'node:child_process';
2
2
  import { readdir, writeFile } from 'node:fs/promises';
3
- import { basename, join } from 'node:path';
3
+ import { basename, dirname, join, resolve } from 'node:path';
4
+ import { classifyLegacySyncedRecord, probeAuthoritativeCompletedRef, } from '../project/list.js';
5
+ import { defaultGitRunner } from '../project/sync/git.js';
6
+ import { listSyncedRecords } from '../project/sync/record.js';
7
+ import { buildSyncTarget } from '../project/sync/ref-sync.js';
8
+ import { probeSyncedTerminalRefs } from '../project/sync/resolve-target.js';
4
9
  import { parseFrontmatterField } from '../shared/frontmatter.js';
5
10
  import { resolveProjectsRoot } from '../shared/oat-paths.js';
11
+ import { resolveScopeRoot } from '../shared/project-scope.js';
6
12
  import { readOatLocalConfig } from '../../config/oat-config.js';
7
13
  import { ensureDir, fileExists } from '../../fs/io.js';
8
14
  const defaultGit = {
@@ -310,22 +316,41 @@ function isTerminalCoordinationProject(kind, phase, phaseStatus) {
310
316
  phase === 'decomposition' &&
311
317
  phaseStatus === 'complete');
312
318
  }
313
- async function listAvailableProjects(repoRoot, projectsRoot) {
314
- const fullRoot = join(repoRoot, projectsRoot);
315
- try {
316
- const entries = await readdir(fullRoot, { withFileTypes: true });
317
- const dirs = entries.filter((e) => e.isDirectory());
318
- if (dirs.length === 0) {
319
- return {
320
- active: '*(No projects found)*',
321
- decompositions: '*(No decompositions found)*',
322
- };
319
+ async function listAvailableProjects(repoRoot, projectsRoot, gitRunner) {
320
+ const lines = [];
321
+ const decompositions = [];
322
+ const materializedSynced = new Set();
323
+ for (const scope of ['shared', 'synced', 'local']) {
324
+ const scopeRoot = resolveScopeRoot(repoRoot, projectsRoot, scope);
325
+ let entries;
326
+ try {
327
+ entries = await readdir(scopeRoot, { withFileTypes: true });
323
328
  }
324
- const lines = [];
325
- const decompositions = [];
326
- for (const dir of dirs) {
327
- const stateFile = join(fullRoot, dir.name, 'state.md');
329
+ catch {
330
+ continue;
331
+ }
332
+ for (const dir of entries.filter((entry) => entry.isDirectory())) {
333
+ const stateFile = join(scopeRoot, dir.name, 'state.md');
328
334
  if (await fileExists(stateFile)) {
335
+ if (scope === 'synced') {
336
+ materializedSynced.add(dir.name);
337
+ const target = buildSyncTarget(repoRoot, projectsRoot, dir.name);
338
+ const terminal = await probeAuthoritativeCompletedRef(target, gitRunner);
339
+ if (terminal) {
340
+ const row = classifyLegacySyncedRecord({
341
+ schemaVersion: 1,
342
+ slug: dir.name,
343
+ scope: 'synced',
344
+ ref: target.ref,
345
+ remote: 'origin',
346
+ status: 'active',
347
+ createdAt: new Date(0).toISOString(),
348
+ completedAt: null,
349
+ }, target.projectPath, terminal);
350
+ lines.push(`- **${dir.name}** - ${row.recommendation.reason}`);
351
+ continue;
352
+ }
353
+ }
329
354
  const phase = (await parseFrontmatterField(stateFile, 'oat_phase')) || 'unknown';
330
355
  const phaseStatus = (await parseFrontmatterField(stateFile, 'oat_phase_status')) ||
331
356
  'in_progress';
@@ -340,19 +365,29 @@ async function listAvailableProjects(repoRoot, projectsRoot) {
340
365
  }
341
366
  }
342
367
  }
343
- return {
344
- active: lines.length > 0 ? lines.join('\n') : '*(No projects found)*',
345
- decompositions: decompositions.length > 0
346
- ? decompositions.join('\n')
347
- : '*(No decompositions found)*',
348
- };
349
368
  }
350
- catch {
351
- return {
352
- active: '*(No projects directory found)*',
353
- decompositions: '*(No decompositions found)*',
354
- };
369
+ const syncedRoot = resolveScopeRoot(repoRoot, projectsRoot, 'synced');
370
+ const records = await listSyncedRecords(syncedRoot);
371
+ for (const record of records) {
372
+ if (materializedSynced.has(record.slug))
373
+ continue;
374
+ const target = buildSyncTarget(repoRoot, projectsRoot, record.slug);
375
+ const authoritative = await probeAuthoritativeCompletedRef(target, gitRunner);
376
+ const probe = authoritative ??
377
+ (record.archiveSourceRefSha
378
+ ? await probeSyncedTerminalRefs(target, record.archiveSourceRefSha, gitRunner)
379
+ : undefined);
380
+ const row = classifyLegacySyncedRecord(record, target.projectPath, probe);
381
+ lines.push(row.kind === 'recorded-absent'
382
+ ? `- **${record.slug}** - checkout absent; oat project pull ${record.slug}`
383
+ : `- **${record.slug}** - ${row.recommendation.reason}`);
355
384
  }
385
+ return {
386
+ active: lines.length > 0 ? lines.join('\n') : '*(No projects found)*',
387
+ decompositions: decompositions.length > 0
388
+ ? decompositions.join('\n')
389
+ : '*(No decompositions found)*',
390
+ };
356
391
  }
357
392
  function buildDashboardMarkdown(project, state, knowledge, staleness, nextStep, projectsList, generatedDate) {
358
393
  const timestamp = `${generatedDate}`;
@@ -453,18 +488,48 @@ export async function generateStateDashboard(options) {
453
488
  const env = options.env ?? process.env;
454
489
  const today = options.today ?? new Date().toISOString().slice(0, 10);
455
490
  const git = options.git ?? defaultGit;
491
+ const gitRunner = options.gitRunner ?? defaultGitRunner;
456
492
  const dashboardPath = join(repoRoot, '.oat', 'state.md');
457
493
  const projectsRoot = await resolveProjectsRoot(repoRoot, env);
458
494
  const hasProjects = await hasAnyProjects(join(repoRoot, projectsRoot));
459
495
  const project = await readActiveProject(repoRoot);
496
+ let terminalNextStep = null;
497
+ if (project.status === 'active' && project.path) {
498
+ const activePath = resolve(repoRoot, project.path);
499
+ const syncedRoot = resolveScopeRoot(repoRoot, projectsRoot, 'synced');
500
+ if (dirname(activePath) === syncedRoot) {
501
+ const target = buildSyncTarget(repoRoot, projectsRoot, project.name);
502
+ const terminal = await probeAuthoritativeCompletedRef(target, gitRunner);
503
+ if (terminal) {
504
+ const row = classifyLegacySyncedRecord({
505
+ schemaVersion: 1,
506
+ slug: project.name,
507
+ scope: 'synced',
508
+ ref: target.ref,
509
+ remote: 'origin',
510
+ status: 'active',
511
+ createdAt: new Date(0).toISOString(),
512
+ completedAt: null,
513
+ }, activePath, terminal);
514
+ project.status =
515
+ row.kind === 'terminal-invalid'
516
+ ? 'terminal ref mismatch'
517
+ : 'terminal cleanup pending';
518
+ terminalNextStep = {
519
+ step: 'oat-project-complete',
520
+ reason: row.recommendation.reason,
521
+ };
522
+ }
523
+ }
524
+ }
460
525
  let state = null;
461
526
  if (project.status === 'active') {
462
527
  state = await readProjectState(repoRoot, project.path);
463
528
  }
464
529
  const knowledge = await readKnowledgeStatus(repoRoot);
465
530
  const staleness = calculateStaleness(knowledge, git, repoRoot, today);
466
- const nextStep = computeNextStep(project, hasProjects, state);
467
- const projectsList = await listAvailableProjects(repoRoot, projectsRoot);
531
+ const nextStep = terminalNextStep ?? computeNextStep(project, hasProjects, state);
532
+ const projectsList = await listAvailableProjects(repoRoot, projectsRoot, gitRunner);
468
533
  const markdown = buildDashboardMarkdown(project, state, knowledge, staleness, nextStep, projectsList, today);
469
534
  await ensureDir(join(repoRoot, '.oat'));
470
535
  await writeFile(dashboardPath, markdown, 'utf8');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-agent-toolkit/cli",
3
- "version": "0.2.50",
3
+ "version": "0.2.51",
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.2.50"
37
+ "@open-agent-toolkit/control-plane": "0.2.51"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@types/node": "^22.10.0",