@open-agent-toolkit/cli 0.1.14 → 0.1.15

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.
@@ -1,6 +1,6 @@
1
1
  {
2
- "cli": "0.1.14",
3
- "docs-config": "0.1.14",
4
- "docs-theme": "0.1.14",
5
- "docs-transforms": "0.1.14"
2
+ "cli": "0.1.15",
3
+ "docs-config": "0.1.15",
4
+ "docs-theme": "0.1.15",
5
+ "docs-transforms": "0.1.15"
6
6
  }
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: oat-review-provide
3
- version: 1.2.0
3
+ version: 1.2.1
4
4
  description: Use when you need an ad-hoc review outside an active OAT project lifecycle. Reviews code or artifacts without project phase state, unlike oat-project-review-provide.
5
5
  argument-hint: '[unstaged|staged|base_branch=<branch>|base_sha=<sha>|<sha1>..<sha2>|--files <path1,path2,...>] [--output <path>] [--mode auto|local|tracked|inline]'
6
6
  disable-model-invocation: true
@@ -137,7 +137,7 @@ bash .agents/skills/oat-review-provide/scripts/resolve-review-output.sh --mode a
137
137
 
138
138
  Policy:
139
139
 
140
- - If `.oat/repo/reviews` exists and is not gitignored, assume user wants tracked active artifacts there.
140
+ - If `.oat/repo/reviews` exists and new review artifacts under it are not gitignored, assume user wants tracked active artifacts there.
141
141
  - Otherwise default to active local `.oat/projects/local/orphan-reviews`.
142
142
  - Do **not** write new review artifacts directly into any `archived/` directory; those are historical locations used after `oat-review-receive` processes a review.
143
143
  - If user preference is unclear, ask and recommend local-only.
@@ -18,7 +18,7 @@ Policy:
18
18
  - If --output is provided, use it directly.
19
19
  - If mode=inline, no artifact file is written.
20
20
  - In auto mode:
21
- - If .oat/repo/reviews exists and is NOT gitignored, use it (tracked convention).
21
+ - If .oat/repo/reviews exists and new review artifacts under it are NOT gitignored, use it (tracked convention).
22
22
  - Otherwise, use .oat/projects/local/orphan-reviews (local-only default).
23
23
  USAGE
24
24
  }
@@ -67,12 +67,22 @@ is_gitignored() {
67
67
  fi
68
68
  }
69
69
 
70
+ artifact_probe_path() {
71
+ local output_dir="$1"
72
+ printf '%s/%s\n' "${output_dir%/}" "ad-hoc-review-gitignore-probe.md"
73
+ }
74
+
75
+ is_output_gitignored() {
76
+ local output_dir="$1"
77
+ is_gitignored "$(artifact_probe_path "$output_dir")"
78
+ }
79
+
70
80
  # Resolve explicit output first
71
81
  if [[ -n "$OUTPUT" ]]; then
72
82
  echo "review_mode=file"
73
83
  echo "output_dir=$OUTPUT"
74
84
  echo "output_kind=custom"
75
- echo "output_gitignored=$(is_gitignored "$OUTPUT")"
85
+ echo "output_gitignored=$(is_output_gitignored "$OUTPUT")"
76
86
  echo "reason=explicit_output"
77
87
  exit 0
78
88
  fi
@@ -93,7 +103,7 @@ if [[ "$MODE" == "tracked" ]]; then
93
103
  echo "review_mode=file"
94
104
  echo "output_dir=$TRACKED_DIR"
95
105
  echo "output_kind=tracked"
96
- echo "output_gitignored=$(is_gitignored "$TRACKED_DIR")"
106
+ echo "output_gitignored=$(is_output_gitignored "$TRACKED_DIR")"
97
107
  echo "reason=forced_tracked"
98
108
  exit 0
99
109
  fi
@@ -102,13 +112,13 @@ if [[ "$MODE" == "local" ]]; then
102
112
  echo "review_mode=file"
103
113
  echo "output_dir=$LOCAL_DIR"
104
114
  echo "output_kind=local"
105
- echo "output_gitignored=$(is_gitignored "$LOCAL_DIR")"
115
+ echo "output_gitignored=$(is_output_gitignored "$LOCAL_DIR")"
106
116
  echo "reason=forced_local"
107
117
  exit 0
108
118
  fi
109
119
 
110
120
  # auto mode
111
- if [[ -d "$TRACKED_DIR" ]] && [[ "$(is_gitignored "$TRACKED_DIR")" == "false" ]]; then
121
+ if [[ -d "$TRACKED_DIR" ]] && [[ "$(is_output_gitignored "$TRACKED_DIR")" == "false" ]]; then
112
122
  echo "review_mode=file"
113
123
  echo "output_dir=$TRACKED_DIR"
114
124
  echo "output_kind=tracked"
@@ -120,5 +130,5 @@ fi
120
130
  echo "review_mode=file"
121
131
  echo "output_dir=$LOCAL_DIR"
122
132
  echo "output_kind=local"
123
- echo "output_gitignored=$(is_gitignored "$LOCAL_DIR")"
133
+ echo "output_gitignored=$(is_output_gitignored "$LOCAL_DIR")"
124
134
  echo "reason=default_local_only"
@@ -10,6 +10,7 @@ interface ProjectNewDependencies {
10
10
  force: boolean;
11
11
  setActive: boolean;
12
12
  refreshDashboard: boolean;
13
+ commit: boolean;
13
14
  }) => Promise<ScaffoldProjectResult>;
14
15
  }
15
16
  export declare function createProjectNewCommand(overrides?: Partial<ProjectNewDependencies>): Command;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/new/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,OAAO,EAAU,MAAM,WAAW,CAAC;AAE5C,OAAO,EAEL,KAAK,mBAAmB,EACxB,KAAK,qBAAqB,EAC3B,MAAM,YAAY,CAAC;AASpB,UAAU,sBAAsB;IAC9B,mBAAmB,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,cAAc,CAAC;IAChE,eAAe,EAAE,CAAC,OAAO,EAAE;QACzB,QAAQ,EAAE,MAAM,CAAC;QACjB,WAAW,EAAE,MAAM,CAAC;QACpB,IAAI,EAAE,mBAAmB,CAAC;QAC1B,KAAK,EAAE,OAAO,CAAC;QACf,SAAS,EAAE,OAAO,CAAC;QACnB,gBAAgB,EAAE,OAAO,CAAC;KAC3B,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAC;CACtC;AAiED,wBAAgB,uBAAuB,CACrC,SAAS,GAAE,OAAO,CAAC,sBAAsB,CAAM,GAC9C,OAAO,CAiCT"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/new/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,OAAO,EAAU,MAAM,WAAW,CAAC;AAE5C,OAAO,EAEL,KAAK,mBAAmB,EACxB,KAAK,qBAAqB,EAC3B,MAAM,YAAY,CAAC;AAgBpB,UAAU,sBAAsB;IAC9B,mBAAmB,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,cAAc,CAAC;IAChE,eAAe,EAAE,CAAC,OAAO,EAAE;QACzB,QAAQ,EAAE,MAAM,CAAC;QACjB,WAAW,EAAE,MAAM,CAAC;QACpB,IAAI,EAAE,mBAAmB,CAAC;QAC1B,KAAK,EAAE,OAAO,CAAC;QACf,SAAS,EAAE,OAAO,CAAC;QACnB,gBAAgB,EAAE,OAAO,CAAC;QAC1B,MAAM,EAAE,OAAO,CAAC;KACjB,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAC;CACtC;AAoFD,wBAAgB,uBAAuB,CACrC,SAAS,GAAE,OAAO,CAAC,sBAAsB,CAAM,GAC9C,OAAO,CAkCT"}
@@ -2,6 +2,11 @@ import { buildCommandContext, } from '../../../app/command-context.js';
2
2
  import { readGlobalOptions } from '../../shared/shared.utils.js';
3
3
  import { Command, Option } from 'commander';
4
4
  import { scaffoldProject as defaultScaffoldProject, } from './scaffold.js';
5
+ const COMMIT_STATUS_MESSAGES = {
6
+ skipped_disabled: 'Scaffold commit: skipped (--no-commit)',
7
+ skipped_no_worktree: 'Scaffold commit: skipped (not a git work tree)',
8
+ skipped_nothing: 'Scaffold commit: skipped (nothing to commit)',
9
+ };
5
10
  const DEFAULT_DEPENDENCIES = {
6
11
  buildCommandContext,
7
12
  scaffoldProject: defaultScaffoldProject,
@@ -18,6 +23,10 @@ function reportSuccess(context, projectName, result) {
18
23
  skippedFiles: result.skippedFiles,
19
24
  activePointerUpdated: result.activePointerUpdated,
20
25
  dashboardRefreshed: result.dashboardRefreshed,
26
+ committed: result.committed,
27
+ commitSha: result.commitSha,
28
+ commitStatus: result.commitStatus,
29
+ commitError: result.commitError,
21
30
  });
22
31
  return;
23
32
  }
@@ -26,6 +35,18 @@ function reportSuccess(context, projectName, result) {
26
35
  if (result.activePointerUpdated) {
27
36
  context.logger.info('Active project updated in local config: .oat/config.local.json');
28
37
  }
38
+ if (result.commitStatus === 'committed') {
39
+ context.logger.info(`Scaffold commit: ${result.commitSha?.slice(0, 7) ?? 'committed'}`);
40
+ }
41
+ else if (result.commitStatus === 'failed') {
42
+ // The scaffold itself succeeded, so this is a warning, not an error: do not
43
+ // change the process exit code. Make clear the baseline was NOT committed.
44
+ const detail = result.commitError ? `: ${result.commitError}` : '';
45
+ context.logger.warn(`Warning: scaffold commit failed${detail}. The scaffolded files were written but NOT committed.`);
46
+ }
47
+ else {
48
+ context.logger.info(COMMIT_STATUS_MESSAGES[result.commitStatus]);
49
+ }
29
50
  }
30
51
  async function runProjectNew(projectName, options, context, dependencies) {
31
52
  try {
@@ -36,6 +57,7 @@ async function runProjectNew(projectName, options, context, dependencies) {
36
57
  force: options.force,
37
58
  setActive: options.setActive,
38
59
  refreshDashboard: options.dashboard,
60
+ commit: options.commit,
39
61
  });
40
62
  reportSuccess(context, projectName, result);
41
63
  process.exitCode = 0;
@@ -65,6 +87,7 @@ export function createProjectNewCommand(overrides = {}) {
65
87
  .option('--force', 'Non-destructive scaffold; create missing files only')
66
88
  .option('--no-set-active', 'Do not update active project in local config')
67
89
  .option('--no-dashboard', 'Do not refresh .oat/state.md after scaffold')
90
+ .option('--no-commit', 'Do not git-commit the scaffolded project directory')
68
91
  .action(async (name, options, command) => {
69
92
  if (name.startsWith('-')) {
70
93
  command.help();
@@ -6,11 +6,24 @@ export interface ScaffoldProjectOptions {
6
6
  force?: boolean;
7
7
  setActive?: boolean;
8
8
  refreshDashboard?: boolean;
9
+ /**
10
+ * Commit the freshly scaffolded project directory so the artifact baseline is
11
+ * git-tracked from t=0. Opt-in (default false) so library callers that manage
12
+ * their own commits (e.g. the project-split flow) are unaffected; only the
13
+ * `oat project new` command enables it by default.
14
+ */
15
+ commit?: boolean;
9
16
  env?: NodeJS.ProcessEnv;
10
17
  today?: string;
11
18
  nowUtc?: string;
12
19
  refreshDashboardCallback?: (repoRoot: string) => void | Promise<void>;
13
20
  }
21
+ /**
22
+ * Classified outcome of the scoped scaffold commit. Distinguishing the skip
23
+ * reasons (and `failed`) lets the CLI surface accurate, distinct messaging
24
+ * instead of collapsing every non-commit into a single benign "skipped" line.
25
+ */
26
+ export type CommitScaffoldStatus = 'committed' | 'skipped_disabled' | 'skipped_no_worktree' | 'skipped_nothing' | 'failed';
14
27
  export interface ScaffoldProjectResult {
15
28
  mode: ProjectScaffoldMode;
16
29
  projectsRoot: string;
@@ -19,6 +32,10 @@ export interface ScaffoldProjectResult {
19
32
  skippedFiles: string[];
20
33
  activePointerUpdated: boolean;
21
34
  dashboardRefreshed: boolean;
35
+ committed: boolean;
36
+ commitSha?: string;
37
+ commitStatus: CommitScaffoldStatus;
38
+ commitError?: string;
22
39
  }
23
40
  export declare function scaffoldProject(options: ScaffoldProjectOptions): Promise<ScaffoldProjectResult>;
24
41
  //# sourceMappingURL=scaffold.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/new/scaffold.ts"],"names":[],"mappings":"AASA,MAAM,MAAM,mBAAmB,GAAG,aAAa,GAAG,OAAO,GAAG,QAAQ,CAAC;AAErE,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,mBAAmB,CAAC;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wBAAwB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACvE;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,mBAAmB,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,oBAAoB,EAAE,OAAO,CAAC;IAC9B,kBAAkB,EAAE,OAAO,CAAC;CAC7B;AAwMD,wBAAsB,eAAe,CACnC,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,qBAAqB,CAAC,CAqDhC"}
1
+ {"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/new/scaffold.ts"],"names":[],"mappings":"AAUA,MAAM,MAAM,mBAAmB,GAAG,aAAa,GAAG,OAAO,GAAG,QAAQ,CAAC;AAErE,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,mBAAmB,CAAC;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;;;;OAKG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wBAAwB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACvE;AAED;;;;GAIG;AACH,MAAM,MAAM,oBAAoB,GAC5B,WAAW,GACX,kBAAkB,GAClB,qBAAqB,GACrB,iBAAiB,GACjB,QAAQ,CAAC;AAEb,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,mBAAmB,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,oBAAoB,EAAE,OAAO,CAAC;IAC9B,kBAAkB,EAAE,OAAO,CAAC;IAC5B,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,oBAAoB,CAAC;IACnC,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAoRD,wBAAsB,eAAe,CACnC,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,qBAAqB,CAAC,CA4EhC"}
@@ -1,3 +1,4 @@
1
+ import { execFileSync } from 'node:child_process';
1
2
  import { mkdir, readFile, writeFile } from 'node:fs/promises';
2
3
  import { join } from 'node:path';
3
4
  import { resolveProjectsRoot } from '../../shared/oat-paths.js';
@@ -105,6 +106,62 @@ function applyTemplateReplacements(template, projectName, today, nowUtc, mode) {
105
106
  async function defaultRefreshDashboard(repoRoot) {
106
107
  await generateStateDashboard({ repoRoot });
107
108
  }
109
+ /**
110
+ * Scoped, fail-safe commit of just the files this run created.
111
+ *
112
+ * Stages and commits only the pathspecs derived from `createdFiles` (under
113
+ * `projectPath`) so unrelated working-tree changes — including pre-existing
114
+ * dirty edits inside the same project directory on a re-run, and the
115
+ * `.oat/state.md` dashboard outside it — are never swept in. The returned
116
+ * status distinguishes a clean commit from each skip reason and from a genuine
117
+ * git failure; on failure the captured git stderr is surfaced via `error`. This
118
+ * never throws: any git error is classified as `failed`, not propagated.
119
+ */
120
+ function commitScaffold(cwd, projectPath, projectName, createdFiles) {
121
+ const run = (args) => execFileSync('git', args, {
122
+ cwd,
123
+ encoding: 'utf8',
124
+ // Capture stderr instead of inheriting it so deliberate skip/failure
125
+ // probes (e.g. tests) do not leak raw `git fatal:` lines to the terminal.
126
+ stdio: ['ignore', 'pipe', 'pipe'],
127
+ }).trim();
128
+ try {
129
+ run(['rev-parse', '--is-inside-work-tree']);
130
+ }
131
+ catch {
132
+ return { status: 'skipped_no_worktree', committed: false };
133
+ }
134
+ // Only commit files this run created. Nothing created => nothing to commit,
135
+ // which guarantees a re-run never touches unrelated working-tree edits.
136
+ if (createdFiles.length === 0) {
137
+ return { status: 'skipped_nothing', committed: false };
138
+ }
139
+ const pathspecs = createdFiles.map((file) => join(projectPath, file));
140
+ try {
141
+ run(['add', '--', ...pathspecs]);
142
+ const staged = run(['diff', '--cached', '--name-only', '--', ...pathspecs]);
143
+ if (staged.length === 0) {
144
+ return { status: 'skipped_nothing', committed: false };
145
+ }
146
+ run([
147
+ 'commit',
148
+ '-m',
149
+ `chore(oat): scaffold ${projectName}`,
150
+ '--',
151
+ ...pathspecs,
152
+ ]);
153
+ const commitSha = run(['rev-parse', 'HEAD']);
154
+ return { status: 'committed', committed: true, commitSha };
155
+ }
156
+ catch (error) {
157
+ const stderr = error && typeof error === 'object' && 'stderr' in error
158
+ ? error.stderr
159
+ : undefined;
160
+ const message = (stderr != null ? stderr.toString().trim() : '') ||
161
+ (error instanceof Error ? error.message : String(error));
162
+ return { status: 'failed', committed: false, error: message };
163
+ }
164
+ }
108
165
  async function scaffoldModeTemplates(repoRoot, projectPath, projectName, mode, today, nowUtc) {
109
166
  const templatesDir = join(repoRoot, '.oat', 'templates');
110
167
  const createdFiles = [];
@@ -169,6 +226,19 @@ export async function scaffoldProject(options) {
169
226
  console.error(`Warning: dashboard refresh failed: ${error instanceof Error ? error.message : String(error)}`);
170
227
  }
171
228
  }
229
+ let committed = false;
230
+ let commitSha;
231
+ let commitStatus = 'skipped_disabled';
232
+ let commitError;
233
+ if (options.commit) {
234
+ // `projectPath` is relative to `repoRoot`, so git must run there for the
235
+ // pathspecs to resolve to the scaffolded files.
236
+ const commitResult = commitScaffold(options.repoRoot, projectPath, options.projectName, createdFiles);
237
+ committed = commitResult.committed;
238
+ commitSha = commitResult.commitSha;
239
+ commitStatus = commitResult.status;
240
+ commitError = commitResult.error;
241
+ }
172
242
  return {
173
243
  mode,
174
244
  projectsRoot,
@@ -177,5 +247,9 @@ export async function scaffoldProject(options) {
177
247
  skippedFiles,
178
248
  activePointerUpdated: setActive,
179
249
  dashboardRefreshed,
250
+ committed,
251
+ commitSha,
252
+ commitStatus,
253
+ commitError,
180
254
  };
181
255
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-agent-toolkit/cli",
3
- "version": "0.1.14",
3
+ "version": "0.1.15",
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.14"
37
+ "@open-agent-toolkit/control-plane": "0.1.15"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@types/node": "^22.10.0",