@open-agent-toolkit/cli 0.1.14 → 0.1.16

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.16",
3
+ "docs-config": "0.1.16",
4
+ "docs-theme": "0.1.16",
5
+ "docs-transforms": "0.1.16"
6
6
  }
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: oat-project-split
3
- version: 1.0.0
3
+ version: 1.1.0
4
4
  description: Use when a discovery or brainstorm should split one broad scope into coordinated OAT child projects.
5
5
  argument-hint: '--plan-file <path>'
6
6
  disable-model-invocation: true
@@ -73,10 +73,25 @@ The run command marks the parent `oat_phase: decomposition` and `oat_phase_statu
73
73
 
74
74
  If a previous split wrote a coordination parent but did not finish all children, the run command resumes from `references/split-plan.json`. Do not reconstruct missing child seed data from slugs alone.
75
75
 
76
+ ## Children Resume At Discovery (Revalidation Contract)
77
+
78
+ A split seeds children with inherited context, but that context is only a **starting point**. By the time an agent resumes a child it may be stale, so children must never be treated as discovered, planned, or implementation-ready just because the split wrote their files.
79
+
80
+ What the run command guarantees, and what reviewers should expect:
81
+
82
+ - **Child `state.md` routes to discovery.** Each child is written with `oat_phase: discovery` and `oat_phase_status: in_progress`, plus preserved `oat_parent`, `oat_siblings`, and `oat_depends_on` links. Resume, progress, and quick-start all key off `state.md`, so they pick the child back up at discovery — not at plan or implementation.
83
+ - **Child `discovery.md` is in progress with an unmet revalidation gate.** It carries `oat_status: in_progress` and `oat_inherited_context_revalidated: false`, an explicit "Inherited Context Revalidation Gate" section, and language stating that parent-derived scope is a seed, not a final decision. The discovery-completion gate blocks marking discovery complete until `oat_inherited_context_revalidated: true`.
84
+ - **Child discovery is never marked complete during split generation.** Seeding only provides a provisional starting point.
85
+ - **Placeholder `plan.md` cannot be mistaken for a real plan.** The seeded `plan.md` is re-marked as a not-started template (`oat_template: true`, `oat_template_name: plan`). Routing decisions read `state.md` (`oat_phase: discovery`), and `oat-project-next` treats `oat_template: true` as a still-a-template signal, so the placeholder never reads as plan-ready.
86
+
87
+ A child sitting at `discovery` / `in_progress` after a split is **expected work-in-progress**, not stale bookkeeping. Do not flag it as drift or "skipped discovery" — revalidating inherited context is the next intended step.
88
+
76
89
  ## Success Criteria
77
90
 
78
91
  - Coordination parent exists and has no `spec.md`, `design.md`, `plan.md`, or `implementation.md`.
79
92
  - Parent `state.md` records `oat_kind: coordination`, ordered `oat_children`, and terminal decomposition status.
80
93
  - `references/split-plan.json` contains the full `SplitPlanDocument`.
81
- - Every child has seeded discovery content, parent/sibling/dependency links, and `oat_inherited_context_revalidated: false`.
94
+ - Every child `state.md` records `oat_phase: discovery` and `oat_phase_status: in_progress` so resume/progress/quick-start route it back to discovery.
95
+ - Every child has seeded discovery content, parent/sibling/dependency links, `oat_inherited_context_revalidated: false`, and an explicit revalidation gate; no child discovery is marked complete by the split.
96
+ - Every child `plan.md` is a not-started template placeholder (`oat_template: true`) that never reads as plan-ready.
82
97
  - `.oat/config.local.json.activeProject` points at the repo-relative initial child path.
@@ -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
  }
@@ -1,6 +1,6 @@
1
1
  import type { ChildPlan } from './child-plan.js';
2
2
  import type { SplitProjectContext } from './write-parent.js';
3
- declare const SEEDED_SECTIONS: readonly ["Origin", "Inherited Context", "Child Scope", "Known Dependencies", "Assumptions To Revalidate", "Likely Workflow Mode", "Sibling Projects"];
3
+ declare const SEEDED_SECTIONS: readonly ["Origin", "Inherited Context", "Inherited Context Revalidation Gate", "Child Scope", "Known Dependencies", "Assumptions To Revalidate", "Likely Workflow Mode", "Sibling Projects"];
4
4
  export interface SeedChildrenResult {
5
5
  childProjectPaths: string[];
6
6
  }
@@ -1 +1 @@
1
- {"version":3,"file":"seed-children.d.ts","sourceRoot":"","sources":["../../../src/projects/split/seed-children.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAE1D,QAAA,MAAM,eAAe,wJAQX,CAAC;AAoFX,MAAM,WAAW,kBAAkB;IACjC,iBAAiB,EAAE,MAAM,EAAE,CAAC;CAC7B;AAED,wBAAsB,YAAY,CAChC,IAAI,EAAE,SAAS,EACf,OAAO,EAAE,mBAAmB,EAC5B,SAAS,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GACtB,OAAO,CAAC,kBAAkB,CAAC,CA4C7B;AAED,OAAO,EAAE,eAAe,EAAE,CAAC"}
1
+ {"version":3,"file":"seed-children.d.ts","sourceRoot":"","sources":["../../../src/projects/split/seed-children.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAE1D,QAAA,MAAM,eAAe,+LASX,CAAC;AA0GX,MAAM,WAAW,kBAAkB;IACjC,iBAAiB,EAAE,MAAM,EAAE,CAAC;CAC7B;AAED,wBAAsB,YAAY,CAChC,IAAI,EAAE,SAAS,EACf,OAAO,EAAE,mBAAmB,EAC5B,SAAS,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GACtB,OAAO,CAAC,kBAAkB,CAAC,CAoD7B;AAED,OAAO,EAAE,eAAe,EAAE,CAAC"}
@@ -7,6 +7,7 @@ import YAML from 'yaml';
7
7
  const SEEDED_SECTIONS = [
8
8
  'Origin',
9
9
  'Inherited Context',
10
+ 'Inherited Context Revalidation Gate',
10
11
  'Child Scope',
11
12
  'Known Dependencies',
12
13
  'Assumptions To Revalidate',
@@ -53,16 +54,38 @@ function renderSeededDiscovery(plan, child) {
53
54
  .map((candidate) => candidate.slug)
54
55
  .filter((slug) => slug !== child.slug);
55
56
  const sections = [
56
- ['Origin', `Split from coordination parent \`${plan.parentSlug}\`.`],
57
+ [
58
+ 'Origin',
59
+ `Split from coordination parent \`${plan.parentSlug}\`. This child was seeded by \`oat-project-split\`; it is **not** discovered, planned, or implementation-ready yet and resumes at discovery.`,
60
+ ],
57
61
  [
58
62
  'Inherited Context',
59
- child.inheritedContext || 'No inherited context provided.',
63
+ [
64
+ child.inheritedContext || 'No inherited context provided.',
65
+ '',
66
+ '> Provisional seed. This context was inherited from the parent split decision. It is a **starting point, not a final scope decision** — treat every inherited claim as an assumption until it is revalidated below.',
67
+ ].join('\n'),
68
+ ],
69
+ [
70
+ 'Inherited Context Revalidation Gate',
71
+ [
72
+ 'This project resumes at **discovery**, not planning or implementation. Inherited context may be stale by the time work resumes, so it must be revalidated before this discovery is completed.',
73
+ '',
74
+ '- [ ] Re-read the inherited context against the current codebase and sibling progress.',
75
+ '- [ ] Confirm or correct the child scope, dependencies, and assumptions below.',
76
+ '- [ ] When revalidation is complete, set `oat_inherited_context_revalidated: true` in this file.',
77
+ '',
78
+ 'Discovery cannot be marked complete — and planning must not begin — while `oat_inherited_context_revalidated: false`. A child left in this state is expected work-in-progress, not stale bookkeeping.',
79
+ ].join('\n'),
60
80
  ],
61
81
  ['Child Scope', child.description ?? child.slug],
62
82
  ['Known Dependencies', bulletList(child.knownDependencies)],
63
83
  [
64
84
  'Assumptions To Revalidate',
65
- '- Revalidate inherited context before completing discovery.',
85
+ [
86
+ '- Revalidate inherited context before completing discovery.',
87
+ '- Confirm the child scope still matches reality before generating a plan.',
88
+ ].join('\n'),
66
89
  ],
67
90
  ['Likely Workflow Mode', 'quick'],
68
91
  ['Sibling Projects', bulletList(siblingSlugs)],
@@ -111,8 +134,16 @@ export async function seedChildren(plan, context, onlySlugs) {
111
134
  oat_siblings: siblings,
112
135
  oat_depends_on: child.knownDependencies,
113
136
  });
137
+ // Children resume at discovery, so their scaffolded plan.md is a
138
+ // placeholder only. Re-mark it as a not-started template (the scaffold
139
+ // strips these markers when rendering) so neither tooling nor agents can
140
+ // mistake the seed for an active or validated plan; routing keys off
141
+ // state.md (oat_phase: discovery) and oat-project-next treats
142
+ // oat_template: true as a still-a-template signal.
114
143
  await updateFrontmatter(join(childRoot, 'plan.md'), {
115
144
  oat_plan_source: 'quick',
145
+ oat_template: true,
146
+ oat_template_name: 'plan',
116
147
  });
117
148
  await writeFile(join(childRoot, 'discovery.md'), renderSeededDiscovery(plan, child), 'utf8');
118
149
  childProjectPaths.push(scaffold.projectPath);
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.16",
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.16"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@types/node": "^22.10.0",