sequant 1.16.0 → 1.17.0

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.
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Worktree discovery for state bootstrapping
3
+ *
4
+ * @module worktree-discovery
5
+ * @example
6
+ * ```typescript
7
+ * import { discoverUntrackedWorktrees } from './worktree-discovery';
8
+ *
9
+ * // Discover worktrees not yet tracked in state
10
+ * const result = await discoverUntrackedWorktrees({ verbose: true });
11
+ * for (const worktree of result.discovered) {
12
+ * console.log(`Found: #${worktree.issueNumber} - ${worktree.title}`);
13
+ * }
14
+ * ```
15
+ */
16
+ import { type Phase } from "./state-schema.js";
17
+ export interface DiscoverOptions {
18
+ /** State file path (default: .sequant/state.json) */
19
+ statePath?: string;
20
+ /** Enable verbose logging */
21
+ verbose?: boolean;
22
+ }
23
+ export interface DiscoveredWorktree {
24
+ /** Issue number extracted from branch name */
25
+ issueNumber: number;
26
+ /** Issue title (fetched from GitHub or placeholder) */
27
+ title: string;
28
+ /** Full path to the worktree */
29
+ worktreePath: string;
30
+ /** Branch name */
31
+ branch: string;
32
+ /** Inferred current phase from logs (if available) */
33
+ inferredPhase?: Phase;
34
+ }
35
+ export interface SkippedWorktree {
36
+ /** Path to the worktree */
37
+ path: string;
38
+ /** Reason it was skipped */
39
+ reason: string;
40
+ }
41
+ export interface DiscoverResult {
42
+ /** Whether discovery was successful */
43
+ success: boolean;
44
+ /** Number of worktrees scanned */
45
+ worktreesScanned: number;
46
+ /** Number of worktrees already tracked */
47
+ alreadyTracked: number;
48
+ /** Discovered worktrees not yet in state */
49
+ discovered: DiscoveredWorktree[];
50
+ /** Worktrees that were skipped (not matching pattern, etc.) */
51
+ skipped: SkippedWorktree[];
52
+ /** Error message if failed */
53
+ error?: string;
54
+ }
55
+ /**
56
+ * Discover worktrees that are not yet tracked in state
57
+ *
58
+ * Scans all git worktrees, identifies those with issue-related branch names,
59
+ * and returns information about worktrees not yet in the state file.
60
+ */
61
+ export declare function discoverUntrackedWorktrees(options?: DiscoverOptions): Promise<DiscoverResult>;
@@ -0,0 +1,229 @@
1
+ /**
2
+ * Worktree discovery for state bootstrapping
3
+ *
4
+ * @module worktree-discovery
5
+ * @example
6
+ * ```typescript
7
+ * import { discoverUntrackedWorktrees } from './worktree-discovery';
8
+ *
9
+ * // Discover worktrees not yet tracked in state
10
+ * const result = await discoverUntrackedWorktrees({ verbose: true });
11
+ * for (const worktree of result.discovered) {
12
+ * console.log(`Found: #${worktree.issueNumber} - ${worktree.title}`);
13
+ * }
14
+ * ```
15
+ */
16
+ import * as fs from "fs";
17
+ import * as path from "path";
18
+ import { spawnSync } from "child_process";
19
+ import { StateManager } from "./state-manager.js";
20
+ import { RunLogSchema, LOG_PATHS } from "./run-log-schema.js";
21
+ /**
22
+ * Parse issue number from a branch name
23
+ *
24
+ * Supports patterns:
25
+ * - feature/<number>-<slug>
26
+ * - issue-<number>
27
+ * - <number>-<slug>
28
+ */
29
+ function parseIssueNumberFromBranch(branch) {
30
+ // Pattern: feature/123-description or feature/123
31
+ const featureMatch = branch.match(/^feature\/(\d+)(?:-|$)/);
32
+ if (featureMatch) {
33
+ return parseInt(featureMatch[1], 10);
34
+ }
35
+ // Pattern: issue-123
36
+ const issueMatch = branch.match(/^issue-(\d+)$/);
37
+ if (issueMatch) {
38
+ return parseInt(issueMatch[1], 10);
39
+ }
40
+ // Pattern: 123-description (bare number prefix)
41
+ const bareMatch = branch.match(/^(\d+)-/);
42
+ if (bareMatch) {
43
+ return parseInt(bareMatch[1], 10);
44
+ }
45
+ return null;
46
+ }
47
+ /**
48
+ * Fetch issue title from GitHub using gh CLI
49
+ *
50
+ * Returns placeholder if gh is not available or fetch fails.
51
+ */
52
+ function fetchIssueTitle(issueNumber) {
53
+ try {
54
+ const result = spawnSync("gh", ["issue", "view", String(issueNumber), "--json", "title", "-q", ".title"], { stdio: "pipe", timeout: 10000 });
55
+ if (result.status === 0 && result.stdout) {
56
+ const title = result.stdout.toString().trim();
57
+ if (title) {
58
+ return title;
59
+ }
60
+ }
61
+ }
62
+ catch {
63
+ // gh not available or error - use placeholder
64
+ }
65
+ return `(title unavailable for #${issueNumber})`;
66
+ }
67
+ function getWorktreeDetails() {
68
+ const result = spawnSync("git", ["worktree", "list", "--porcelain"], {
69
+ stdio: "pipe",
70
+ });
71
+ if (result.status !== 0) {
72
+ return [];
73
+ }
74
+ const output = result.stdout.toString();
75
+ const worktrees = [];
76
+ let current = {};
77
+ for (const line of output.split("\n")) {
78
+ if (line.startsWith("worktree ")) {
79
+ // Start of new worktree entry
80
+ if (current.path) {
81
+ worktrees.push(current);
82
+ }
83
+ current = { path: line.substring(9) };
84
+ }
85
+ else if (line.startsWith("HEAD ")) {
86
+ current.head = line.substring(5);
87
+ }
88
+ else if (line.startsWith("branch refs/heads/")) {
89
+ current.branch = line.substring(18);
90
+ }
91
+ else if (line === "" && current.path) {
92
+ // End of entry
93
+ worktrees.push(current);
94
+ current = {};
95
+ }
96
+ }
97
+ // Don't forget the last entry
98
+ if (current.path && current.branch) {
99
+ worktrees.push(current);
100
+ }
101
+ return worktrees;
102
+ }
103
+ /**
104
+ * Infer the current phase for an issue by checking logs
105
+ */
106
+ function inferPhaseFromLogs(issueNumber) {
107
+ const logPath = LOG_PATHS.project;
108
+ if (!fs.existsSync(logPath)) {
109
+ return undefined;
110
+ }
111
+ try {
112
+ const files = fs.readdirSync(logPath).filter((f) => f.endsWith(".json"));
113
+ // Sort by timestamp (newest first)
114
+ files.sort().reverse();
115
+ for (const file of files) {
116
+ try {
117
+ const content = fs.readFileSync(path.join(logPath, file), "utf-8");
118
+ const logData = JSON.parse(content);
119
+ const log = RunLogSchema.safeParse(logData);
120
+ if (!log.success)
121
+ continue;
122
+ // Find this issue in the log
123
+ const issueLog = log.data.issues.find((i) => i.issueNumber === issueNumber);
124
+ if (issueLog && issueLog.phases.length > 0) {
125
+ // Return the last executed phase
126
+ const lastPhase = issueLog.phases[issueLog.phases.length - 1];
127
+ return lastPhase.phase;
128
+ }
129
+ }
130
+ catch {
131
+ continue;
132
+ }
133
+ }
134
+ }
135
+ catch {
136
+ return undefined;
137
+ }
138
+ return undefined;
139
+ }
140
+ /**
141
+ * Discover worktrees that are not yet tracked in state
142
+ *
143
+ * Scans all git worktrees, identifies those with issue-related branch names,
144
+ * and returns information about worktrees not yet in the state file.
145
+ */
146
+ export async function discoverUntrackedWorktrees(options = {}) {
147
+ try {
148
+ const worktrees = getWorktreeDetails();
149
+ const discovered = [];
150
+ const skipped = [];
151
+ let alreadyTracked = 0;
152
+ // Get existing state
153
+ const manager = new StateManager({
154
+ statePath: options.statePath,
155
+ verbose: options.verbose,
156
+ });
157
+ const state = await manager.getState();
158
+ const trackedIssues = new Set(Object.keys(state.issues).map((n) => parseInt(n, 10)));
159
+ for (const worktree of worktrees) {
160
+ // Skip if no branch (detached HEAD)
161
+ if (!worktree.branch) {
162
+ skipped.push({
163
+ path: worktree.path,
164
+ reason: "detached HEAD (no branch)",
165
+ });
166
+ continue;
167
+ }
168
+ // Skip main/master branches
169
+ if (worktree.branch === "main" || worktree.branch === "master") {
170
+ skipped.push({
171
+ path: worktree.path,
172
+ reason: "main/master branch (not a feature worktree)",
173
+ });
174
+ continue;
175
+ }
176
+ // Try to parse issue number from branch
177
+ const issueNumber = parseIssueNumberFromBranch(worktree.branch);
178
+ if (issueNumber === null) {
179
+ skipped.push({
180
+ path: worktree.path,
181
+ reason: `branch name doesn't match issue pattern: ${worktree.branch}`,
182
+ });
183
+ continue;
184
+ }
185
+ // Check if already tracked
186
+ if (trackedIssues.has(issueNumber)) {
187
+ alreadyTracked++;
188
+ if (options.verbose) {
189
+ console.log(` Already tracked: #${issueNumber} (${worktree.branch})`);
190
+ }
191
+ continue;
192
+ }
193
+ // Fetch title from GitHub
194
+ if (options.verbose) {
195
+ console.log(` Fetching title for #${issueNumber}...`);
196
+ }
197
+ const title = fetchIssueTitle(issueNumber);
198
+ // Try to infer phase from logs
199
+ const inferredPhase = inferPhaseFromLogs(issueNumber);
200
+ discovered.push({
201
+ issueNumber,
202
+ title,
203
+ worktreePath: worktree.path,
204
+ branch: worktree.branch,
205
+ inferredPhase,
206
+ });
207
+ if (options.verbose) {
208
+ console.log(` Discovered: #${issueNumber} - ${title}${inferredPhase ? ` (phase: ${inferredPhase})` : ""}`);
209
+ }
210
+ }
211
+ return {
212
+ success: true,
213
+ worktreesScanned: worktrees.length,
214
+ alreadyTracked,
215
+ discovered,
216
+ skipped,
217
+ };
218
+ }
219
+ catch (error) {
220
+ return {
221
+ success: false,
222
+ worktreesScanned: 0,
223
+ alreadyTracked: 0,
224
+ discovered: [],
225
+ skipped: [],
226
+ error: String(error),
227
+ };
228
+ }
229
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sequant",
3
- "version": "1.16.0",
3
+ "version": "1.17.0",
4
4
  "description": "Quantize your development workflow - Sequential AI phases with quality gates",
5
5
  "type": "module",
6
6
  "bin": {
@@ -50,7 +50,7 @@
50
50
  "bugs": {
51
51
  "url": "https://github.com/sequant-io/sequant/issues"
52
52
  },
53
- "homepage": "https://github.com/sequant-io/sequant#readme",
53
+ "homepage": "https://sequant.io",
54
54
  "engines": {
55
55
  "node": ">=18.0.0"
56
56
  },
@@ -62,9 +62,9 @@
62
62
  "chokidar": "^5.0.0",
63
63
  "cli-table3": "^0.6.5",
64
64
  "commander": "^12.1.0",
65
- "diff": "^7.0.0",
65
+ "diff": "^8.0.3",
66
66
  "gradient-string": "^3.0.0",
67
- "hono": "^4.11.4",
67
+ "hono": "^4.12.1",
68
68
  "inquirer": "^12.3.2",
69
69
  "open": "^11.0.0",
70
70
  "ora": "^8.2.0",
@@ -73,7 +73,6 @@
73
73
  },
74
74
  "devDependencies": {
75
75
  "@eslint/js": "^9.39.2",
76
- "@types/diff": "^7.0.0",
77
76
  "@types/gradient-string": "^1.1.6",
78
77
  "@types/inquirer": "^9.0.7",
79
78
  "@types/node": "^22.10.5",
@@ -64,7 +64,7 @@ When invoked as `/exec`, your job is to:
64
64
  ```bash
65
65
  # Check for existing phase markers
66
66
  phase_data=$(gh issue view <issue-number> --json comments --jq '[.comments[].body]' | \
67
- grep -o '{[^}]*}' | grep '"phase"' | tail -1)
67
+ grep -o '{[^}]*}' | grep '"phase"' | tail -1 || true)
68
68
 
69
69
  if [[ -n "$phase_data" ]]; then
70
70
  phase=$(echo "$phase_data" | jq -r '.phase')
@@ -127,7 +127,7 @@ git log --oneline -3 --stat
127
127
 
128
128
  # Check for existing PRs or branches for this issue
129
129
  gh pr list --search "<issue-number>"
130
- git branch -a | grep -i "<issue-number>"
130
+ git branch -a | grep -i "<issue-number>" || true
131
131
  ```
132
132
 
133
133
  **Why this matters:** After context restoration, PRs may have merged, branches may have changed, or work may already be complete. Always verify before creating duplicate work.
@@ -97,7 +97,7 @@ When invoked as `/fullsolve <issue-number>`, execute the complete issue resoluti
97
97
  ```bash
98
98
  # Get all phase markers from issue comments
99
99
  comments_json=$(gh issue view <issue-number> --json comments --jq '[.comments[].body]')
100
- markers=$(echo "$comments_json" | grep -o '{[^}]*}' | grep '"phase"')
100
+ markers=$(echo "$comments_json" | grep -o '{[^}]*}' | grep '"phase"' || true)
101
101
 
102
102
  if [[ -n "$markers" ]]; then
103
103
  echo "Phase markers detected:"
@@ -147,7 +147,7 @@ When posting progress comments after each phase, append the appropriate marker:
147
147
  git log --oneline -5 --stat
148
148
 
149
149
  # Check for any existing work on this issue
150
- git branch -a | grep -i "<issue-number>"
150
+ git branch -a | grep -i "<issue-number>" || true
151
151
  gh pr list --search "<issue-number>"
152
152
  ```
153
153
 
@@ -345,10 +345,10 @@ while test_iteration < 3:
345
345
 
346
346
  ```bash
347
347
  # Type safety
348
- git diff main...HEAD | grep -E ":\s*any[,)]|as any"
348
+ git diff main...HEAD | grep -E ":\s*any[,)]|as any" || true
349
349
 
350
350
  # Deleted tests
351
- git diff main...HEAD --diff-filter=D --name-only | grep -E "\.test\."
351
+ git diff main...HEAD --diff-filter=D --name-only | grep -E "\.test\." || true
352
352
 
353
353
  # Scope check
354
354
  git diff main...HEAD --name-only | wc -l
@@ -636,6 +636,20 @@ For multiple issues, run `/fullsolve` on each sequentially:
636
636
 
637
637
  Each issue gets its own worktree, PR, and quality validation.
638
638
 
639
+ ### Post-Batch: Merge Verification
640
+
641
+ After processing a batch, run `sequant merge` to catch cross-issue integration gaps before merging:
642
+
643
+ ```bash
644
+ /fullsolve 218
645
+ /fullsolve 219
646
+ /fullsolve 220
647
+ sequant merge --check # Verify no cross-issue conflicts
648
+ /merger 218 219 220 # Merge all issues
649
+ ```
650
+
651
+ `sequant merge --check` detects merge conflicts, template mirroring gaps, and file overlaps at zero AI cost. See `docs/reference/merge-command.md`.
652
+
639
653
  ---
640
654
 
641
655
  ## Output Verification
@@ -101,7 +101,7 @@ Extract:
101
101
 
102
102
  Find the worktree for this issue:
103
103
  ```bash
104
- git worktree list | grep -E "feature.*<issue-number>"
104
+ git worktree list | grep -E "feature.*<issue-number>" || true
105
105
  ```
106
106
 
107
107
  Or check:
@@ -46,13 +46,13 @@ When invoked as `/qa`, your job is to:
46
46
  comments_json=$(gh issue view <issue-number> --json comments --jq '[.comments[].body]')
47
47
  exec_completed=$(echo "$comments_json" | \
48
48
  grep -o '{[^}]*}' | grep '"phase"' | \
49
- jq -r 'select(.phase == "exec" and .status == "completed")' 2>/dev/null)
49
+ jq -r 'select(.phase == "exec" and .status == "completed")' 2>/dev/null || true)
50
50
 
51
51
  if [[ -z "$exec_completed" ]]; then
52
52
  # Check if any exec marker exists at all
53
53
  exec_any=$(echo "$comments_json" | \
54
54
  grep -o '{[^}]*}' | grep '"phase"' | \
55
- jq -r 'select(.phase == "exec")' 2>/dev/null)
55
+ jq -r 'select(.phase == "exec")' 2>/dev/null || true)
56
56
 
57
57
  if [[ -n "$exec_any" ]]; then
58
58
  echo "⚠️ Exec phase not completed (status: $(echo "$exec_any" | jq -r '.status')). Run /exec first."
@@ -177,7 +177,7 @@ If no feature worktree exists (work was done directly on main):
177
177
 
178
178
  **Add RLS check if admin files modified:**
179
179
  ```bash
180
- admin_modified=$(git diff main...HEAD --name-only | grep -E "^app/admin/" | head -1)
180
+ admin_modified=$(git diff main...HEAD --name-only | grep -E "^app/admin/" | head -1 || true)
181
181
  ```
182
182
 
183
183
  See [quality-gates.md](references/quality-gates.md) for detailed verdict synthesis.
@@ -306,10 +306,10 @@ See [quality-gates.md](references/quality-gates.md) for detailed verdict criteri
306
306
 
307
307
  ```bash
308
308
  # Type safety
309
- type_issues=$(git diff main...HEAD | grep -E ":\s*any[,)]|as any" | wc -l | xargs)
309
+ type_issues=$(git diff main...HEAD | grep -E ":\s*any[,)]|as any" | wc -l | xargs || echo "0")
310
310
 
311
311
  # Deleted tests
312
- deleted_tests=$(git diff main...HEAD --diff-filter=D --name-only | grep -E "\\.test\\.|\\spec\\." | wc -l | xargs)
312
+ deleted_tests=$(git diff main...HEAD --diff-filter=D --name-only | grep -E "\\.test\\.|\\spec\\." | wc -l | xargs || echo "0")
313
313
 
314
314
  # Scope check
315
315
  files_changed=$(git diff main...HEAD --name-only | wc -l | xargs)
@@ -355,7 +355,7 @@ If verdict is `READY_FOR_MERGE` or `AC_MET_BUT_NOT_A_PLUS`:
355
355
 
356
356
  **Detection:**
357
357
  ```bash
358
- scripts_changed=$(git diff main...HEAD --name-only | grep "^scripts/" | wc -l | xargs)
358
+ scripts_changed=$(git diff main...HEAD --name-only | grep "^scripts/" | wc -l | xargs || echo "0")
359
359
  if [[ $scripts_changed -gt 0 ]]; then
360
360
  echo "Script changes detected. Run /verify before READY_FOR_MERGE"
361
361
  fi
@@ -100,10 +100,10 @@ When analyzing issues, check if `--base` flag should be recommended.
100
100
 
101
101
  ```bash
102
102
  # Check for feature branch references in issue body
103
- gh issue view <issue-number> --json body --jq '.body' | grep -iE "(feature/|branch from|based on|part of.*feature)"
103
+ gh issue view <issue-number> --json body --jq '.body' | grep -iE "(feature/|branch from|based on|part of.*feature)" || true
104
104
 
105
105
  # Check issue labels for feature context
106
- gh issue view <issue-number> --json labels --jq '.labels[].name' | grep -iE "(dashboard|feature-|epic-)"
106
+ gh issue view <issue-number> --json labels --jq '.labels[].name' | grep -iE "(dashboard|feature-|epic-)" || true
107
107
 
108
108
  # Check if project has defaultBase configured
109
109
  # Use the Read tool to check project settings
@@ -130,13 +130,13 @@ When analyzing multiple issues, determine if `--chain` flag should be recommende
130
130
 
131
131
  ```bash
132
132
  # Check for dependency keywords in issue body
133
- gh issue view <issue-number> --json body --jq '.body' | grep -iE "(depends on|blocked by|requires|after #|builds on)"
133
+ gh issue view <issue-number> --json body --jq '.body' | grep -iE "(depends on|blocked by|requires|after #|builds on)" || true
134
134
 
135
135
  # Check for sequence labels
136
- gh issue view <issue-number> --json labels --jq '.labels[].name' | grep -iE "(part-[0-9]|step-[0-9]|phase-[0-9])"
136
+ gh issue view <issue-number> --json labels --jq '.labels[].name' | grep -iE "(part-[0-9]|step-[0-9]|phase-[0-9])" || true
137
137
 
138
138
  # Check for related issue references
139
- gh issue view <issue-number> --json body --jq '.body' | grep -oE "#[0-9]+"
139
+ gh issue view <issue-number> --json body --jq '.body' | grep -oE "#[0-9]+" || true
140
140
  ```
141
141
 
142
142
  **Recommend `--chain` when:**
@@ -201,7 +201,7 @@ Before generating output, check for in-flight work that may conflict:
201
201
 
202
202
  ```bash
203
203
  # List open worktrees
204
- git worktree list --porcelain 2>/dev/null | grep "^worktree" | cut -d' ' -f2
204
+ git worktree list --porcelain 2>/dev/null | grep "^worktree" | cut -d' ' -f2 || true
205
205
 
206
206
  # For each worktree, get changed files
207
207
  git -C <worktree-path> diff --name-only main...HEAD 2>/dev/null
@@ -477,6 +477,28 @@ npx sequant run 152 --quality-loop --max-iterations 5
477
477
  npx sequant run 152 --dry-run
478
478
  ```
479
479
 
480
+ ### Post-Run: Merge Verification
481
+
482
+ After batch execution, run merge checks before merging:
483
+
484
+ ```bash
485
+ # Verify feature branches are safe to merge (auto-detects issues from last run)
486
+ npx sequant merge --check
487
+
488
+ # Full scan including residual pattern detection
489
+ npx sequant merge --scan
490
+
491
+ # Post results to each PR
492
+ npx sequant merge --check --post
493
+ ```
494
+
495
+ **Recommended workflow:**
496
+ ```bash
497
+ npx sequant run 152 153 154 # implement
498
+ npx sequant merge --check # verify cross-issue integration
499
+ /merger 152 153 154 # merge
500
+ ```
501
+
480
502
  ### Custom Base Branch
481
503
 
482
504
  The `--base` flag specifies which branch to create worktrees from:
@@ -37,7 +37,7 @@ When invoked as `/spec`, your job is to:
37
37
  ```bash
38
38
  # Check for existing phase markers
39
39
  phase_data=$(gh issue view <issue-number> --json comments --jq '[.comments[].body]' | \
40
- grep -o '{[^}]*}' | grep '"phase"' | tail -1)
40
+ grep -o '{[^}]*}' | grep '"phase"' | tail -1 || true)
41
41
 
42
42
  if [[ -n "$phase_data" ]]; then
43
43
  phase=$(echo "$phase_data" | jq -r '.phase')
@@ -91,8 +91,8 @@ When called like `/spec <freeform description>`:
91
91
  ```bash
92
92
  # Extract AC from issue body and store in state
93
93
  npx tsx -e "
94
- import { extractAcceptanceCriteria } from './src/lib/ac-parser.js';
95
- import { StateManager } from './src/lib/workflow/state-manager.js';
94
+ import { extractAcceptanceCriteria } from './src/lib/ac-parser.ts';
95
+ import { StateManager } from './src/lib/workflow/state-manager.ts';
96
96
 
97
97
  const issueBody = \`<ISSUE_BODY_HERE>\`;
98
98
  const issueNumber = <ISSUE_NUMBER>;
@@ -103,13 +103,14 @@ console.log('Extracted AC:', JSON.stringify(ac, null, 2));
103
103
 
104
104
  if (ac.items.length > 0) {
105
105
  const manager = new StateManager();
106
- // Initialize issue if not exists
107
- const existing = await manager.getIssueState(issueNumber);
108
- if (!existing) {
109
- await manager.initializeIssue(issueNumber, issueTitle);
110
- }
111
- await manager.updateAcceptanceCriteria(issueNumber, ac);
112
- console.log('AC stored in state for issue #' + issueNumber);
106
+ (async () => {
107
+ const existing = await manager.getIssueState(issueNumber);
108
+ if (!existing) {
109
+ await manager.initializeIssue(issueNumber, issueTitle);
110
+ }
111
+ await manager.updateAcceptanceCriteria(issueNumber, ac);
112
+ console.log('AC stored in state for issue #' + issueNumber);
113
+ })();
113
114
  }
114
115
  "
115
116
  ```
@@ -136,8 +137,8 @@ The parser supports multiple formats:
136
137
  ```bash
137
138
  # Lint AC for quality issues (skip if --skip-ac-lint flag is set)
138
139
  npx tsx -e "
139
- import { parseAcceptanceCriteria } from './src/lib/ac-parser.js';
140
- import { lintAcceptanceCriteria, formatACLintResults } from './src/lib/ac-linter.js';
140
+ import { parseAcceptanceCriteria } from './src/lib/ac-parser.ts';
141
+ import { lintAcceptanceCriteria, formatACLintResults } from './src/lib/ac-linter.ts';
141
142
 
142
143
  const issueBody = \`<ISSUE_BODY_HERE>\`;
143
144
 
@@ -230,12 +231,12 @@ Before creating the implementation plan, check if a custom base branch should be
230
231
 
231
232
  1. **Check for feature branch references in issue body**:
232
233
  ```bash
233
- gh issue view <issue> --json body --jq '.body' | grep -iE "(feature/|branch from|based on|part of.*feature)"
234
+ gh issue view <issue> --json body --jq '.body' | grep -iE "(feature/|branch from|based on|part of.*feature)" || true
234
235
  ```
235
236
 
236
237
  2. **Check issue labels for feature context**:
237
238
  ```bash
238
- gh issue view <issue> --json labels --jq '.labels[].name' | grep -iE "(dashboard|feature-|epic-)"
239
+ gh issue view <issue> --json labels --jq '.labels[].name' | grep -iE "(dashboard|feature-|epic-)" || true
239
240
  ```
240
241
 
241
242
  3. **Check if project has defaultBase configured**:
@@ -409,7 +410,7 @@ First, check if a `/solve` comment already exists for this issue:
409
410
 
410
411
  ```bash
411
412
  # Check issue comments for solve workflow
412
- gh issue view <issue-number> --json comments --jq '.comments[].body' | grep -l "## Solve Workflow for Issues:"
413
+ gh issue view <issue-number> --json comments --jq '.comments[].body' | grep -l "## Solve Workflow for Issues:" || true
413
414
  ```
414
415
 
415
416
  **If solve comment found:**
@@ -413,7 +413,7 @@ If an AC has verification method "N/A - Trivial", skip test generation and note
413
413
  If generating file-based tests (Unit Test, Integration Test), find the worktree:
414
414
 
415
415
  ```bash
416
- git worktree list | grep -E "feature.*<issue-number>"
416
+ git worktree list | grep -E "feature.*<issue-number>" || true
417
417
  ```
418
418
 
419
419
  Or check: