@open-agent-toolkit/cli 0.2.24 → 0.2.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.
Files changed (46) hide show
  1. package/assets/agents/oat-reviewer.md +12 -1
  2. package/assets/docs/cli-utilities/configuration.md +44 -15
  3. package/assets/docs/reference/cli-reference.md +1 -1
  4. package/assets/docs/workflows/projects/dispatch-ceiling.md +67 -16
  5. package/assets/docs/workflows/projects/reviews.md +19 -10
  6. package/assets/public-package-versions.json +4 -4
  7. package/assets/skills/oat-project-autonomous/references/gate-inventory.md +4 -4
  8. package/assets/skills/oat-project-document/references/docs/autonomy-contract.md +4 -4
  9. package/assets/skills/oat-project-implement/SKILL.md +9 -11
  10. package/assets/skills/oat-project-implement/references/dispatch-and-dry-run.md +18 -9
  11. package/assets/skills/oat-project-implement/references/docs/autonomy-contract.md +4 -4
  12. package/assets/skills/oat-project-implement/references/phase-execution.md +42 -4
  13. package/assets/skills/oat-project-pr-final/references/docs/autonomy-contract.md +4 -4
  14. package/assets/skills/oat-project-quick-start/references/docs/autonomy-contract.md +4 -4
  15. package/assets/skills/oat-project-review-provide/SKILL.md +124 -26
  16. package/assets/skills/oat-project-review-provide-remote/SKILL.md +64 -15
  17. package/assets/skills/oat-project-review-receive/SKILL.md +21 -1
  18. package/assets/skills/oat-project-review-receive-remote/SKILL.md +39 -2
  19. package/assets/skills/oat-review-provide-remote/SKILL.md +55 -12
  20. package/assets/templates/plan.md +13 -7
  21. package/dist/commands/config/index.d.ts.map +1 -1
  22. package/dist/commands/config/index.js +35 -11
  23. package/dist/commands/project/dispatch-ceiling/index.d.ts.map +1 -1
  24. package/dist/commands/project/dispatch-ceiling/index.js +90 -0
  25. package/dist/commands/review/latest.d.ts.map +1 -1
  26. package/dist/commands/review/latest.js +22 -9
  27. package/dist/config/dispatch-notices.d.ts +8 -0
  28. package/dist/config/dispatch-notices.d.ts.map +1 -0
  29. package/dist/config/dispatch-notices.js +79 -0
  30. package/dist/config/dispatch-policy-options.d.ts +2 -0
  31. package/dist/config/dispatch-policy-options.d.ts.map +1 -1
  32. package/dist/config/dispatch-policy-options.js +14 -2
  33. package/dist/config/resolve.js +1 -1
  34. package/dist/providers/identity/dispatch-report.d.ts +17 -0
  35. package/dist/providers/identity/dispatch-report.d.ts.map +1 -1
  36. package/dist/providers/identity/dispatch-report.js +30 -0
  37. package/dist/review-remote/body-builder.d.ts +12 -3
  38. package/dist/review-remote/body-builder.d.ts.map +1 -1
  39. package/dist/review-remote/body-builder.js +11 -0
  40. package/dist/review-remote/marker-parser.d.ts +8 -3
  41. package/dist/review-remote/marker-parser.d.ts.map +1 -1
  42. package/dist/review-remote/marker-parser.js +13 -2
  43. package/dist/review-remote/narrowing.d.ts +44 -8
  44. package/dist/review-remote/narrowing.d.ts.map +1 -1
  45. package/dist/review-remote/narrowing.js +106 -10
  46. package/package.json +2 -2
@@ -13,7 +13,7 @@
13
13
  */
14
14
  /** Token that opens the marker comment block. */
15
15
  export declare const MARKER_BLOCK_OPEN = "oat-review-metadata";
16
- export type ReviewInvocation = 'manual' | 'auto';
16
+ export type ReviewInvocation = 'manual' | 'auto' | 'gate';
17
17
  export interface MarkerBlock {
18
18
  /** Always `true` for an OAT provide-remote review; the discriminator. */
19
19
  oat_provide_remote: true;
@@ -26,8 +26,13 @@ export interface MarkerBlock {
26
26
  * `null` value) discriminates project rail from ad-hoc rail.
27
27
  */
28
28
  oat_project?: string;
29
- /** How the review was invoked. Defaults to `manual` when omitted. */
30
- oat_review_invocation: ReviewInvocation;
29
+ /**
30
+ * How the review was invoked. Missing and unknown values stay undefined so
31
+ * legacy markers cannot silently acquire lifecycle lineage.
32
+ */
33
+ oat_review_invocation?: ReviewInvocation;
34
+ /** Exact gate target. Required before a gate marker can establish lineage. */
35
+ oat_gate_target?: string;
31
36
  /** Forward-compat bag for unknown marker keys. Omitted when none seen. */
32
37
  extras?: Record<string, string>;
33
38
  }
@@ -1 +1 @@
1
- {"version":3,"file":"marker-parser.d.ts","sourceRoot":"","sources":["../../src/review-remote/marker-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,iDAAiD;AACjD,eAAO,MAAM,iBAAiB,wBAAwB,CAAC;AAavD,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,MAAM,CAAC;AAEjD,MAAM,WAAW,WAAW;IAC1B,yEAAyE;IACzE,kBAAkB,EAAE,IAAI,CAAC;IACzB,oDAAoD;IACpD,mBAAmB,EAAE,MAAM,CAAC;IAC5B,gEAAgE;IAChE,gBAAgB,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qEAAqE;IACrE,qBAAqB,EAAE,gBAAgB,CAAC;IACxC,0EAA0E;IAC1E,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAUD;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAqEjE"}
1
+ {"version":3,"file":"marker-parser.d.ts","sourceRoot":"","sources":["../../src/review-remote/marker-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,iDAAiD;AACjD,eAAO,MAAM,iBAAiB,wBAAwB,CAAC;AAavD,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,MAAM,GAAG,MAAM,CAAC;AAE1D,MAAM,WAAW,WAAW;IAC1B,yEAAyE;IACzE,kBAAkB,EAAE,IAAI,CAAC;IACzB,oDAAoD;IACpD,mBAAmB,EAAE,MAAM,CAAC;IAC5B,gEAAgE;IAChE,gBAAgB,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,qBAAqB,CAAC,EAAE,gBAAgB,CAAC;IACzC,8EAA8E;IAC9E,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,0EAA0E;IAC1E,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAWD;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CA+EjE"}
@@ -26,6 +26,7 @@ const KNOWN_KEYS = new Set([
26
26
  'oat_review_scope',
27
27
  'oat_project',
28
28
  'oat_review_invocation',
29
+ 'oat_gate_target',
29
30
  ]);
30
31
  /**
31
32
  * Parse the first OAT marker block out of a posted-review body.
@@ -73,17 +74,27 @@ export function parseMarkerBlock(body) {
73
74
  return null;
74
75
  }
75
76
  const invocationRaw = raw['oat_review_invocation'];
76
- const oat_review_invocation = invocationRaw === 'auto' ? 'auto' : 'manual';
77
+ const oat_review_invocation = invocationRaw === 'manual' ||
78
+ invocationRaw === 'auto' ||
79
+ invocationRaw === 'gate'
80
+ ? invocationRaw
81
+ : undefined;
77
82
  const block = {
78
83
  oat_provide_remote: true,
79
84
  oat_review_head_sha: headSha,
80
85
  oat_review_scope: scope,
81
- oat_review_invocation,
82
86
  };
87
+ if (oat_review_invocation !== undefined) {
88
+ block.oat_review_invocation = oat_review_invocation;
89
+ }
83
90
  // Project key existence (not a null value) discriminates the rail.
84
91
  if ('oat_project' in raw && raw['oat_project'] !== '') {
85
92
  block.oat_project = raw['oat_project'];
86
93
  }
94
+ const gateTarget = raw['oat_gate_target']?.trim();
95
+ if (gateTarget) {
96
+ block.oat_gate_target = gateTarget;
97
+ }
87
98
  const extras = {};
88
99
  for (const [key, value] of Object.entries(raw)) {
89
100
  if (!KNOWN_KEYS.has(key)) {
@@ -9,8 +9,15 @@
9
9
  * range, so a rebased/force-pushed/shallow prior SHA never produces a
10
10
  * misleading partial range.
11
11
  */
12
- import type { ReviewInvocation } from './marker-parser.js';
12
+ import type { MarkerBlock, ReviewInvocation } from './marker-parser.js';
13
13
  export type ReviewRail = 'ad-hoc' | 'project';
14
+ export type ReviewLineage = {
15
+ kind: 'lifecycle';
16
+ } | {
17
+ kind: 'gate';
18
+ target: string;
19
+ };
20
+ export type ReviewInvocationKind = ReviewInvocation;
14
21
  /** A prior provide-remote review, distilled from its parsed marker block. */
15
22
  export interface PriorReview {
16
23
  /** Full 40-char SHA recorded by the prior review. */
@@ -19,10 +26,34 @@ export interface PriorReview {
19
26
  scope: string;
20
27
  /** Project path — present only for project-rail reviews. */
21
28
  project?: string;
22
- invocation: ReviewInvocation;
29
+ invocation?: ReviewInvocationKind;
30
+ /** Review lineage; absent on legacy records, which are not eligible. */
31
+ lineage?: ReviewLineage;
23
32
  /** ISO-8601 review submission timestamp, used for descending sort. */
24
33
  submittedAt: string;
25
34
  }
35
+ /** Provenance persisted on one tracked Reviews ledger row. */
36
+ export interface ReviewLedgerProvenance {
37
+ reviewedHead: string;
38
+ scope: string;
39
+ project?: string;
40
+ invocation?: string;
41
+ gateTarget?: string;
42
+ artifact: string;
43
+ submittedAt: string;
44
+ }
45
+ /**
46
+ * Normalize one parsed GitHub marker into a narrowing candidate. Unknown
47
+ * invocation and target-less gate markers preserve their metadata but carry no
48
+ * lineage, so tuple matching rejects them and fails open.
49
+ */
50
+ export declare function priorReviewFromMarker(marker: MarkerBlock, submittedAt: string): PriorReview;
51
+ /**
52
+ * Normalize durable ledger provenance into the same candidate shape used by
53
+ * artifact-sourced reviews. Both sources then pass through {@link matchesTuple}
54
+ * and cannot drift into separate lineage predicates.
55
+ */
56
+ export declare function priorReviewFromLedger(row: ReviewLedgerProvenance): PriorReview;
26
57
  /**
27
58
  * Narrow git surface needed by the guard. Callers pass a worktree-bound
28
59
  * implementation (rich-context mode) or a fetch-capable one (diff-only mode);
@@ -35,6 +66,8 @@ export interface GitInvoker {
35
66
  isAncestor(sha: string, head: string): Promise<boolean>;
36
67
  /** `git fetch origin <sha>:<ref>` — fetch a single ref (diff-only mode). */
37
68
  fetchRef(sha: string): Promise<boolean>;
69
+ /** Files changed in `<priorSha>..<headSha>`. */
70
+ changedFiles(priorSha: string, headSha: string): Promise<string[]>;
38
71
  }
39
72
  export interface NarrowingInput {
40
73
  reviews: PriorReview[];
@@ -43,30 +76,33 @@ export interface NarrowingInput {
43
76
  project: string | null;
44
77
  /** Current scope token, or `ad-hoc`. */
45
78
  scope: string;
79
+ /** Current review lineage, including the target for gate invocations. */
80
+ lineage: ReviewLineage;
46
81
  /** Current PR HEAD SHA. */
47
82
  headSha: string;
48
83
  git: GitInvoker;
49
84
  /** `--narrow` was passed explicitly: guard failure becomes a hard error. */
50
85
  forceNarrow?: boolean;
51
- /** `workflow.autoNarrowReReviewScope === true`: never prompt. */
52
- autoNarrow?: boolean;
86
+ /** Resolved preference: unset and true narrow; false forces full scope. */
87
+ narrowingPreference?: boolean;
53
88
  /** Diff-only mode (no ephemeral worktree): fetch the single ref first. */
54
89
  diffOnly?: boolean;
55
90
  }
56
- export type FullScopeReason = 'no-prior-review' | 'stale-sha';
91
+ export type FullScopeReason = 'narrowing-disabled' | 'no-prior-review' | 'stale-sha';
92
+ export type RangeClassification = 'empty' | 'bookkeeping-only' | 'substantive';
57
93
  export interface NarrowRangeResult {
58
94
  kind: 'narrow-range';
59
95
  priorSha: string;
60
96
  headSha: string;
61
- /** Whether the caller still owes the user a confirm prompt. */
62
- prompted: boolean;
97
+ classification: RangeClassification;
98
+ /** Why classification conservatively fell back, when enumeration failed. */
99
+ classificationReason?: 'changed-files-unavailable';
63
100
  }
64
101
  export interface FullScopeFallbackResult {
65
102
  kind: 'full-scope-fallback';
66
103
  reason: FullScopeReason;
67
104
  /** The stale SHA that triggered the fallback, when applicable. */
68
105
  priorSha?: string;
69
- prompted: boolean;
70
106
  }
71
107
  export interface HardErrorResult {
72
108
  kind: 'hard-error';
@@ -1 +1 @@
1
- {"version":3,"file":"narrowing.d.ts","sourceRoot":"","sources":["../../src/review-remote/narrowing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAExD,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,SAAS,CAAC;AAE9C,6EAA6E;AAC7E,MAAM,WAAW,WAAW;IAC1B,qDAAqD;IACrD,OAAO,EAAE,MAAM,CAAC;IAChB,4CAA4C;IAC5C,KAAK,EAAE,MAAM,CAAC;IACd,4DAA4D;IAC5D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,gBAAgB,CAAC;IAC7B,sEAAsE;IACtE,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;GAIG;AACH,MAAM,WAAW,UAAU;IACzB,+DAA+D;IAC/D,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC5C,yEAAyE;IACzE,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACxD,4EAA4E;IAC5E,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACzC;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,IAAI,EAAE,UAAU,CAAC;IACjB,4EAA4E;IAC5E,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,wCAAwC;IACxC,KAAK,EAAE,MAAM,CAAC;IACd,2BAA2B;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,UAAU,CAAC;IAChB,4EAA4E;IAC5E,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,iEAAiE;IACjE,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,MAAM,eAAe,GAAG,iBAAiB,GAAG,WAAW,CAAC;AAE9D,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,cAAc,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,+DAA+D;IAC/D,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,qBAAqB,CAAC;IAC5B,MAAM,EAAE,eAAe,CAAC;IACxB,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,YAAY,CAAC;IACnB,MAAM,EAAE,WAAW,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,MAAM,eAAe,GACvB,iBAAiB,GACjB,uBAAuB,GACvB,eAAe,CAAC;AAiDpB;;GAEG;AACH,wBAAsB,mBAAmB,CACvC,KAAK,EAAE,cAAc,GACpB,OAAO,CAAC,eAAe,CAAC,CAuC1B"}
1
+ {"version":3,"file":"narrowing.d.ts","sourceRoot":"","sources":["../../src/review-remote/narrowing.ts"],"names":[],"mappings":"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"}
@@ -9,16 +9,85 @@
9
9
  * range, so a rebased/force-pushed/shallow prior SHA never produces a
10
10
  * misleading partial range.
11
11
  */
12
- /** Does a prior review match the current `(rail, project, scope)` tuple? */
12
+ const FULL_COMMIT_SHA = /^[0-9a-f]{40}$/i;
13
+ /**
14
+ * Normalize one parsed GitHub marker into a narrowing candidate. Unknown
15
+ * invocation and target-less gate markers preserve their metadata but carry no
16
+ * lineage, so tuple matching rejects them and fails open.
17
+ */
18
+ export function priorReviewFromMarker(marker, submittedAt) {
19
+ const lineage = markerLineage(marker);
20
+ return {
21
+ headSha: marker.oat_review_head_sha,
22
+ scope: marker.oat_review_scope,
23
+ ...(marker.oat_project ? { project: marker.oat_project } : {}),
24
+ ...(marker.oat_review_invocation
25
+ ? { invocation: marker.oat_review_invocation }
26
+ : {}),
27
+ ...(lineage ? { lineage } : {}),
28
+ submittedAt,
29
+ };
30
+ }
31
+ function markerLineage(marker) {
32
+ if (marker.oat_review_invocation === 'manual' ||
33
+ marker.oat_review_invocation === 'auto') {
34
+ return { kind: 'lifecycle' };
35
+ }
36
+ const gateTarget = marker.oat_gate_target?.trim();
37
+ return marker.oat_review_invocation === 'gate' && gateTarget
38
+ ? { kind: 'gate', target: gateTarget }
39
+ : undefined;
40
+ }
41
+ /**
42
+ * Normalize durable ledger provenance into the same candidate shape used by
43
+ * artifact-sourced reviews. Both sources then pass through {@link matchesTuple}
44
+ * and cannot drift into separate lineage predicates.
45
+ */
46
+ export function priorReviewFromLedger(row) {
47
+ const invocation = isReviewInvocationKind(row.invocation)
48
+ ? row.invocation
49
+ : undefined;
50
+ const lineage = ledgerLineage(row);
51
+ return {
52
+ headSha: row.reviewedHead,
53
+ scope: row.scope,
54
+ ...(row.project ? { project: row.project } : {}),
55
+ ...(invocation ? { invocation } : {}),
56
+ ...(lineage ? { lineage } : {}),
57
+ submittedAt: row.submittedAt,
58
+ };
59
+ }
60
+ function isReviewInvocationKind(invocation) {
61
+ return (invocation === 'manual' || invocation === 'auto' || invocation === 'gate');
62
+ }
63
+ function ledgerLineage(row) {
64
+ if (row.invocation === 'manual' || row.invocation === 'auto') {
65
+ return { kind: 'lifecycle' };
66
+ }
67
+ const gateTarget = row.gateTarget?.trim();
68
+ return row.invocation === 'gate' && gateTarget
69
+ ? { kind: 'gate', target: gateTarget }
70
+ : undefined;
71
+ }
72
+ /** Does a prior review match the current rail, project, scope, and lineage? */
13
73
  function matchesTuple(review, input) {
74
+ if (!FULL_COMMIT_SHA.test(review.headSha)) {
75
+ return false;
76
+ }
77
+ const sameLineage = review.lineage !== undefined &&
78
+ review.lineage.kind === input.lineage.kind &&
79
+ (input.lineage.kind === 'lifecycle' ||
80
+ (review.lineage.kind === 'gate' &&
81
+ review.lineage.target === input.lineage.target));
14
82
  if (input.rail === 'ad-hoc') {
15
83
  // Ad-hoc: scope sentinel must match AND there must be no project key.
16
- return review.scope === 'ad-hoc' && review.project === undefined;
84
+ return (review.scope === 'ad-hoc' && review.project === undefined && sameLineage);
17
85
  }
18
86
  // Project rail: same project AND same scope.
19
87
  return (review.project !== undefined &&
20
88
  review.project === input.project &&
21
- review.scope === input.scope);
89
+ review.scope === input.scope &&
90
+ sameLineage);
22
91
  }
23
92
  function mostRecentMatch(input) {
24
93
  const matches = input.reviews.filter((r) => matchesTuple(r, input));
@@ -48,16 +117,47 @@ async function guardPasses(priorSha, input) {
48
117
  }
49
118
  return input.git.isAncestor(priorSha, input.headSha);
50
119
  }
120
+ async function classifyRange(priorSha, input) {
121
+ let files;
122
+ try {
123
+ files = await input.git.changedFiles(priorSha, input.headSha);
124
+ }
125
+ catch {
126
+ // Classification is reporting-only, so enumeration failure must not block
127
+ // dispatch. Treat unknown work as substantive and disclose the fallback.
128
+ return {
129
+ classification: 'substantive',
130
+ classificationReason: 'changed-files-unavailable',
131
+ };
132
+ }
133
+ if (files.length === 0) {
134
+ return { classification: 'empty' };
135
+ }
136
+ const projectPrefix = input.project === null ? null : `${input.project.replace(/\/+$/, '')}/`;
137
+ // Path-only classification is sufficient while it is reporting-only. If it
138
+ // ever authorizes skipping work, strengthen it with lifecycle corroboration.
139
+ return {
140
+ classification: projectPrefix !== null &&
141
+ files.every((file) => file.startsWith(projectPrefix))
142
+ ? 'bookkeeping-only'
143
+ : 'substantive',
144
+ };
145
+ }
51
146
  /**
52
147
  * Pick the narrowing target for the current re-review pass.
53
148
  */
54
149
  export async function pickNarrowingTarget(input) {
150
+ if (input.narrowingPreference === false && !input.forceNarrow) {
151
+ return {
152
+ kind: 'full-scope-fallback',
153
+ reason: 'narrowing-disabled',
154
+ };
155
+ }
55
156
  const prior = mostRecentMatch(input);
56
157
  if (!prior) {
57
158
  return {
58
159
  kind: 'full-scope-fallback',
59
160
  reason: 'no-prior-review',
60
- prompted: false,
61
161
  };
62
162
  }
63
163
  const passed = await guardPasses(prior.headSha, input);
@@ -66,8 +166,7 @@ export async function pickNarrowingTarget(input) {
66
166
  kind: 'narrow-range',
67
167
  priorSha: prior.headSha,
68
168
  headSha: input.headSha,
69
- // Auto-narrow skips the prompt; otherwise the caller still confirms.
70
- prompted: !input.autoNarrow,
169
+ ...(await classifyRange(prior.headSha, input)),
71
170
  };
72
171
  }
73
172
  // Guard failed. The user explicitly asked to narrow → hard error.
@@ -78,13 +177,10 @@ export async function pickNarrowingTarget(input) {
78
177
  priorSha: prior.headSha,
79
178
  };
80
179
  }
81
- // Otherwise fall back to full scope. Auto-narrow surfaces this as the
82
- // auto-fallback notice (no prompt); manual mode does not prompt here either —
83
- // the fallback is informational.
180
+ // Otherwise fail open to full scope and preserve the guard failure reason.
84
181
  return {
85
182
  kind: 'full-scope-fallback',
86
183
  reason: 'stale-sha',
87
184
  priorSha: prior.headSha,
88
- prompted: false,
89
185
  };
90
186
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-agent-toolkit/cli",
3
- "version": "0.2.24",
3
+ "version": "0.2.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.2.24"
37
+ "@open-agent-toolkit/control-plane": "0.2.26"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@types/node": "^22.10.0",