sequant 1.13.4 → 1.14.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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sequant",
3
3
  "description": "Structured workflow system for Claude Code - GitHub issue resolution with spec, exec, test, and QA phases",
4
- "version": "1.13.4",
4
+ "version": "1.14.0",
5
5
  "author": {
6
6
  "name": "admarble",
7
7
  "email": "github@admarble.com"
package/README.md CHANGED
@@ -9,11 +9,12 @@ Solve GitHub issues with structured phases and quality gates — from issue to m
9
9
 
10
10
  ## Quick Start
11
11
 
12
- ### Install via npm (Recommended)
12
+ ### Install
13
13
 
14
14
  ```bash
15
15
  # In your project directory
16
16
  npm install sequant
17
+ npx sequant init # Install skills to your project
17
18
  npx sequant doctor # Verify setup
18
19
  ```
19
20
 
@@ -22,18 +23,6 @@ To update:
22
23
  npm update sequant
23
24
  ```
24
25
 
25
- ### Alternative: Install as Claude Code Plugin
26
-
27
- > **Note:** Plugin updates via `/plugin update` may not work reliably. Use `npm update sequant` to get latest skills.
28
-
29
- ```bash
30
- # Add the Sequant marketplace
31
- /plugin marketplace add admarble/sequant
32
-
33
- # Install the plugin
34
- /plugin install sequant
35
- ```
36
-
37
26
  ### Start Using
38
27
 
39
28
  Then in Claude Code:
@@ -238,7 +227,6 @@ See [Customization Guide](docs/guides/customization.md) for all options.
238
227
  - [Run Command](docs/reference/run-command.md)
239
228
  - [Git Workflows](docs/guides/git-workflows.md)
240
229
  - [Customization](docs/guides/customization.md)
241
- - [Plugin Updates & Versioning](docs/internal/plugin-updates.md)
242
230
  - [Troubleshooting](docs/troubleshooting.md)
243
231
 
244
232
  Stack guides: [Next.js](docs/stacks/nextjs.md) · [Rust](docs/stacks/rust.md) · [Python](docs/stacks/python.md) · [Go](docs/stacks/go.md)
@@ -249,7 +237,6 @@ Stack guides: [Next.js](docs/stacks/nextjs.md) · [Rust](docs/stacks/rust.md) ·
249
237
 
250
238
  ### Reporting Issues
251
239
 
252
- - **Plugin issues:** [Plugin Feedback template](https://github.com/admarble/sequant/issues/new?template=plugin-feedback.yml)
253
240
  - **Bug reports:** [Bug template](https://github.com/admarble/sequant/issues/new?template=bug.yml)
254
241
  - **Feature requests:** [Feature template](https://github.com/admarble/sequant/issues/new?template=feature.yml)
255
242
  - **Questions:** [GitHub Discussions](https://github.com/admarble/sequant/discussions)
@@ -605,6 +605,8 @@ async function executePhase(issueNumber, phase, config, sessionId, worktreePath,
605
605
  // Determine working directory and environment
606
606
  const shouldUseWorktree = worktreePath && ISOLATED_PHASES.includes(phase);
607
607
  const cwd = shouldUseWorktree ? worktreePath : process.cwd();
608
+ // Track stderr for error diagnostics (declared outside try for catch access)
609
+ let capturedStderr = "";
608
610
  try {
609
611
  // Check if shutdown is in progress
610
612
  if (shutdownManager?.shuttingDown) {
@@ -667,6 +669,15 @@ async function executePhase(issueNumber, phase, config, sessionId, worktreePath,
667
669
  env,
668
670
  // Pass MCP servers for headless mode (AC-2)
669
671
  ...(mcpServers ? { mcpServers } : {}),
672
+ // Capture stderr for debugging (helps diagnose early exit failures)
673
+ stderr: (data) => {
674
+ capturedStderr += data;
675
+ if (config.verbose) {
676
+ spinner?.pause();
677
+ process.stderr.write(chalk.red(data));
678
+ spinner?.resume();
679
+ }
680
+ },
670
681
  },
671
682
  });
672
683
  // Stream and process messages
@@ -791,11 +802,15 @@ async function executePhase(issueNumber, phase, config, sessionId, worktreePath,
791
802
  error: `Timeout after ${config.phaseTimeout}s`,
792
803
  };
793
804
  }
805
+ // Include stderr in error message if available (helps diagnose early exit failures)
806
+ const stderrSuffix = capturedStderr
807
+ ? `\nStderr: ${capturedStderr.slice(0, 500)}`
808
+ : "";
794
809
  return {
795
810
  phase,
796
811
  success: false,
797
812
  durationSeconds,
798
- error,
813
+ error: error + stderrSuffix,
799
814
  };
800
815
  }
801
816
  }
@@ -51,7 +51,7 @@ export declare const colors: {
51
51
  failed: import("chalk").ChalkInstance | ((s: string) => string);
52
52
  };
53
53
  /**
54
- * Get the ASCII logo with optional gradient
54
+ * Get the ASCII logo with brand color
55
55
  */
56
56
  export declare function logo(): string;
57
57
  /**
@@ -9,7 +9,6 @@ import ora from "ora";
9
9
  import boxen from "boxen";
10
10
  // cli-table3 uses CommonJS `export =` syntax, default import works with esModuleInterop
11
11
  import Table from "cli-table3";
12
- import gradient from "gradient-string";
13
12
  import { isCI, isStdoutTTY } from "./tty.js";
14
13
  /**
15
14
  * Global UI configuration state
@@ -109,27 +108,26 @@ function isLegacyWindows() {
109
108
  // ============================================================================
110
109
  /**
111
110
  * Static ASCII logo for SEQUANT branding
112
- * Pre-generated to avoid ~1MB figlet font bundle
111
+ * Clean, legible design inspired by the sequant logo
112
+ * Q has distinctive tail like the brand logo
113
113
  */
114
- const ASCII_LOGO = `
115
- _____ _____ _____ _ _ _____ _ _ _____
116
- / ___/ ___| _ | | | | _ | \\ | |_ _|
117
- \\ \`--.\\ \`--.| | | | | | | | | | \\| | | |
118
- \`--. \\\`--. \\ \\_/ / | | \\_| |_| . \` | | |
119
- /\\__/ /\\__/ /\\___/\\ |_| |\\___/\\_|\\_/ \\_/
120
- \\____/\\____/ \\___/
121
- `.trimStart();
114
+ const ASCII_LOGO = ` ██████ ███████ ██████ ██ ██ █████ ███ ██ ████████
115
+ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██
116
+ █████ █████ ██ ██ ██ ██ ███████ ██ ██ ██ ██
117
+ ██ ██ ██ ▄▄ ██ ██ ██ ██ ██ ██ ██ ██ ██
118
+ ██████ ███████ ██████ ██████ ██ ██ ██ ████ ██
119
+ ▀▀
120
+ `;
122
121
  /**
123
- * Get the ASCII logo with optional gradient
122
+ * Get the ASCII logo with brand color
124
123
  */
125
124
  export function logo() {
126
125
  if (config.jsonMode || config.minimal)
127
126
  return "";
128
127
  if (config.noColor || !config.isTTY)
129
128
  return ASCII_LOGO;
130
- // Apply gradient for color terminals
131
- const sequantGradient = gradient(["#00D4FF", "#7B68EE", "#FF6B9D"]);
132
- return sequantGradient(ASCII_LOGO);
129
+ // Apply brand orange color
130
+ return chalk.hex("#FF8012")(ASCII_LOGO);
133
131
  }
134
132
  /**
135
133
  * Get the banner (logo + tagline)
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Scope Analyzer
3
+ *
4
+ * Detects bundled features and analyzes issue scope using heuristics:
5
+ * - AC clustering by functional area
6
+ * - Title verb detection (multiple verbs = multiple features)
7
+ * - Directory spread analysis
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * import { detectFeatures, analyzeScope } from './analyzer';
12
+ *
13
+ * const criteria = parseAcceptanceCriteria(issueBody);
14
+ * const detection = detectFeatures(criteria, issueTitle);
15
+ * console.log(`Feature count: ${detection.featureCount}`);
16
+ * ```
17
+ */
18
+ import type { AcceptanceCriterion } from "../workflow/state-schema.js";
19
+ import type { ACCluster, FeatureDetection, NonGoals, ScopeAssessmentConfig } from "./types.js";
20
+ /**
21
+ * Cluster AC items by functional area based on keywords
22
+ *
23
+ * @param criteria - Parsed acceptance criteria
24
+ * @returns Array of AC clusters
25
+ */
26
+ export declare function clusterACByKeyword(criteria: AcceptanceCriterion[]): ACCluster[];
27
+ /**
28
+ * Detect action verbs in issue title
29
+ *
30
+ * @param title - Issue title
31
+ * @returns Array of detected verbs
32
+ */
33
+ export declare function detectTitleVerbs(title: string): string[];
34
+ /**
35
+ * Estimate directory spread from AC descriptions
36
+ *
37
+ * Looks for patterns that suggest different areas of the codebase:
38
+ * - File path mentions (src/, lib/, components/)
39
+ * - Module references
40
+ * - Component type mentions
41
+ *
42
+ * @param criteria - Parsed acceptance criteria
43
+ * @returns Estimated number of distinct directories
44
+ */
45
+ export declare function estimateDirectorySpread(criteria: AcceptanceCriterion[]): {
46
+ spread: number;
47
+ directories: string[];
48
+ };
49
+ /**
50
+ * Calculate feature count based on multiple signals
51
+ *
52
+ * Algorithm:
53
+ * 1. Count distinct AC clusters (if >1, suggests multiple features)
54
+ * 2. Count action verbs in title (multiple verbs = multiple features)
55
+ * 3. Consider directory spread (wide spread suggests complexity)
56
+ *
57
+ * @param clusters - AC clusters from keyword analysis
58
+ * @param titleVerbs - Detected verbs from title
59
+ * @param directorySpread - Number of directories touched
60
+ * @returns Estimated feature count
61
+ */
62
+ export declare function calculateFeatureCount(clusters: ACCluster[], titleVerbs: string[], directorySpread: number): number;
63
+ /**
64
+ * Detect features in issue based on AC items and title
65
+ *
66
+ * @param criteria - Parsed acceptance criteria
67
+ * @param title - Issue title
68
+ * @returns Feature detection result
69
+ */
70
+ export declare function detectFeatures(criteria: AcceptanceCriterion[], title: string): FeatureDetection;
71
+ /**
72
+ * Parse non-goals section from issue body
73
+ *
74
+ * Looks for a "Non-Goals" or "Out of Scope" section with checkbox items.
75
+ *
76
+ * @param issueBody - Full issue body markdown
77
+ * @returns Non-goals extraction result
78
+ */
79
+ export declare function parseNonGoals(issueBody: string): NonGoals;
80
+ /**
81
+ * Check if an issue should skip scope assessment (trivial issue)
82
+ *
83
+ * @param acCount - Number of acceptance criteria
84
+ * @param directorySpread - Estimated directory spread
85
+ * @param config - Scope assessment configuration
86
+ * @returns Whether to skip assessment
87
+ */
88
+ export declare function shouldSkipAssessment(acCount: number, directorySpread: number, config?: ScopeAssessmentConfig): {
89
+ skip: boolean;
90
+ reason?: string;
91
+ };
@@ -0,0 +1,310 @@
1
+ /**
2
+ * Scope Analyzer
3
+ *
4
+ * Detects bundled features and analyzes issue scope using heuristics:
5
+ * - AC clustering by functional area
6
+ * - Title verb detection (multiple verbs = multiple features)
7
+ * - Directory spread analysis
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * import { detectFeatures, analyzeScope } from './analyzer';
12
+ *
13
+ * const criteria = parseAcceptanceCriteria(issueBody);
14
+ * const detection = detectFeatures(criteria, issueTitle);
15
+ * console.log(`Feature count: ${detection.featureCount}`);
16
+ * ```
17
+ */
18
+ import { DEFAULT_SCOPE_CONFIG } from "./types.js";
19
+ /**
20
+ * Keywords for clustering AC items by functional area
21
+ */
22
+ const CLUSTER_KEYWORDS = {
23
+ auth: [
24
+ "auth",
25
+ "login",
26
+ "logout",
27
+ "session",
28
+ "password",
29
+ "token",
30
+ "jwt",
31
+ "oauth",
32
+ "sso",
33
+ ],
34
+ ui: [
35
+ "display",
36
+ "show",
37
+ "render",
38
+ "button",
39
+ "form",
40
+ "modal",
41
+ "page",
42
+ "component",
43
+ "ui",
44
+ "dashboard",
45
+ ],
46
+ api: [
47
+ "api",
48
+ "endpoint",
49
+ "route",
50
+ "request",
51
+ "response",
52
+ "http",
53
+ "rest",
54
+ "graphql",
55
+ ],
56
+ data: [
57
+ "database",
58
+ "db",
59
+ "query",
60
+ "store",
61
+ "save",
62
+ "persist",
63
+ "cache",
64
+ "storage",
65
+ ],
66
+ perf: [
67
+ "performance",
68
+ "optimize",
69
+ "speed",
70
+ "fast",
71
+ "slow",
72
+ "latency",
73
+ "throughput",
74
+ ],
75
+ config: ["config", "setting", "option", "preference", "environment", "env"],
76
+ test: ["test", "spec", "coverage", "mock", "stub", "fixture"],
77
+ docs: ["document", "readme", "guide", "tutorial", "example"],
78
+ error: ["error", "exception", "fail", "handle", "catch", "throw"],
79
+ validation: ["valid", "invalid", "check", "verify", "sanitize"],
80
+ };
81
+ /**
82
+ * Action verbs that indicate distinct features when multiple are present
83
+ */
84
+ const ACTION_VERBS = [
85
+ "add",
86
+ "create",
87
+ "implement",
88
+ "build",
89
+ "fix",
90
+ "update",
91
+ "refactor",
92
+ "remove",
93
+ "delete",
94
+ "improve",
95
+ "optimize",
96
+ "migrate",
97
+ "convert",
98
+ "integrate",
99
+ "enable",
100
+ "disable",
101
+ ];
102
+ /**
103
+ * Cluster AC items by functional area based on keywords
104
+ *
105
+ * @param criteria - Parsed acceptance criteria
106
+ * @returns Array of AC clusters
107
+ */
108
+ export function clusterACByKeyword(criteria) {
109
+ const clusters = new Map();
110
+ const unclassified = [];
111
+ for (const ac of criteria) {
112
+ const descLower = ac.description.toLowerCase();
113
+ let matched = false;
114
+ for (const [keyword, patterns] of Object.entries(CLUSTER_KEYWORDS)) {
115
+ if (patterns.some((p) => descLower.includes(p))) {
116
+ const existing = clusters.get(keyword) ?? [];
117
+ existing.push(ac.id);
118
+ clusters.set(keyword, existing);
119
+ matched = true;
120
+ break; // Only assign to first matching cluster
121
+ }
122
+ }
123
+ if (!matched) {
124
+ unclassified.push(ac.id);
125
+ }
126
+ }
127
+ // Add unclassified as a separate cluster if not empty
128
+ if (unclassified.length > 0) {
129
+ clusters.set("other", unclassified);
130
+ }
131
+ // Convert to array format
132
+ return Array.from(clusters.entries()).map(([keyword, acIds]) => ({
133
+ keyword,
134
+ acIds,
135
+ count: acIds.length,
136
+ }));
137
+ }
138
+ /**
139
+ * Detect action verbs in issue title
140
+ *
141
+ * @param title - Issue title
142
+ * @returns Array of detected verbs
143
+ */
144
+ export function detectTitleVerbs(title) {
145
+ const titleLower = title.toLowerCase();
146
+ const detected = [];
147
+ for (const verb of ACTION_VERBS) {
148
+ // Match word boundaries to avoid partial matches
149
+ const regex = new RegExp(`\\b${verb}\\b`, "i");
150
+ if (regex.test(titleLower)) {
151
+ detected.push(verb);
152
+ }
153
+ }
154
+ return detected;
155
+ }
156
+ /**
157
+ * Estimate directory spread from AC descriptions
158
+ *
159
+ * Looks for patterns that suggest different areas of the codebase:
160
+ * - File path mentions (src/, lib/, components/)
161
+ * - Module references
162
+ * - Component type mentions
163
+ *
164
+ * @param criteria - Parsed acceptance criteria
165
+ * @returns Estimated number of distinct directories
166
+ */
167
+ export function estimateDirectorySpread(criteria) {
168
+ const directories = new Set();
169
+ // Common directory patterns
170
+ const dirPatterns = [
171
+ /\b(src|lib|utils|helpers)\b/i,
172
+ /\b(components|pages|views|layouts)\b/i,
173
+ /\b(api|routes|handlers|controllers)\b/i,
174
+ /\b(models|schemas|types|interfaces)\b/i,
175
+ /\b(tests?|__tests__|specs?)\b/i,
176
+ /\b(scripts|bin|cli)\b/i,
177
+ /\b(config|settings)\b/i,
178
+ /\b(docs|documentation)\b/i,
179
+ /\b(styles|css|scss)\b/i,
180
+ /\b(hooks|contexts?|providers?)\b/i,
181
+ /\b(services|queries|mutations)\b/i,
182
+ /\b(middleware|plugins?)\b/i,
183
+ ];
184
+ for (const ac of criteria) {
185
+ const desc = ac.description;
186
+ for (const pattern of dirPatterns) {
187
+ const match = desc.match(pattern);
188
+ if (match) {
189
+ directories.add(match[1].toLowerCase());
190
+ }
191
+ }
192
+ }
193
+ return {
194
+ spread: directories.size,
195
+ directories: Array.from(directories),
196
+ };
197
+ }
198
+ /**
199
+ * Calculate feature count based on multiple signals
200
+ *
201
+ * Algorithm:
202
+ * 1. Count distinct AC clusters (if >1, suggests multiple features)
203
+ * 2. Count action verbs in title (multiple verbs = multiple features)
204
+ * 3. Consider directory spread (wide spread suggests complexity)
205
+ *
206
+ * @param clusters - AC clusters from keyword analysis
207
+ * @param titleVerbs - Detected verbs from title
208
+ * @param directorySpread - Number of directories touched
209
+ * @returns Estimated feature count
210
+ */
211
+ export function calculateFeatureCount(clusters, titleVerbs, directorySpread) {
212
+ // Base: number of distinct clusters with >1 AC items
213
+ const significantClusters = clusters.filter((c) => c.count >= 2).length;
214
+ // Additional signal: multiple verbs in title
215
+ const verbSignal = Math.max(0, titleVerbs.length - 1);
216
+ // Directory spread signal (normalized)
217
+ const dirSignal = directorySpread >= 4 ? 1 : 0;
218
+ // Combine signals with weights
219
+ // Clusters are primary signal, title and directories are secondary
220
+ const rawCount = significantClusters + verbSignal * 0.5 + dirSignal * 0.5;
221
+ // Minimum is 1 feature, round to nearest integer
222
+ return Math.max(1, Math.round(rawCount));
223
+ }
224
+ /**
225
+ * Detect features in issue based on AC items and title
226
+ *
227
+ * @param criteria - Parsed acceptance criteria
228
+ * @param title - Issue title
229
+ * @returns Feature detection result
230
+ */
231
+ export function detectFeatures(criteria, title) {
232
+ const clusters = clusterACByKeyword(criteria);
233
+ const titleVerbs = detectTitleVerbs(title);
234
+ const { spread, directories } = estimateDirectorySpread(criteria);
235
+ const featureCount = calculateFeatureCount(clusters, titleVerbs, spread);
236
+ return {
237
+ featureCount,
238
+ clusters,
239
+ multipleVerbs: titleVerbs.length > 1,
240
+ titleVerbs,
241
+ directorySpread: spread,
242
+ directories,
243
+ };
244
+ }
245
+ /**
246
+ * Parse non-goals section from issue body
247
+ *
248
+ * Looks for a "Non-Goals" or "Out of Scope" section with checkbox items.
249
+ *
250
+ * @param issueBody - Full issue body markdown
251
+ * @returns Non-goals extraction result
252
+ */
253
+ export function parseNonGoals(issueBody) {
254
+ const items = [];
255
+ // Find Non-Goals section (case-insensitive)
256
+ const sectionPattern = /##\s*(?:Non[- ]?Goals|Out\s+of\s+Scope|Scope\s+Boundaries)\s*\n([\s\S]*?)(?=\n##|\n---|$)/i;
257
+ const sectionMatch = issueBody.match(sectionPattern);
258
+ if (!sectionMatch) {
259
+ return {
260
+ items: [],
261
+ found: false,
262
+ warning: "Non-Goals section not found. Consider adding scope boundaries.",
263
+ };
264
+ }
265
+ const sectionContent = sectionMatch[1];
266
+ // Extract checkbox items or list items
267
+ const itemPattern = /^[-*]\s*(?:\[[ x]\]\s*)?(.+)$/gim;
268
+ let match;
269
+ while ((match = itemPattern.exec(sectionContent)) !== null) {
270
+ const item = match[1].trim();
271
+ if (item && !item.startsWith("#")) {
272
+ items.push(item);
273
+ }
274
+ }
275
+ if (items.length === 0) {
276
+ return {
277
+ items: [],
278
+ found: true,
279
+ warning: "Non-Goals section is empty. Add explicit scope boundaries.",
280
+ };
281
+ }
282
+ return {
283
+ items,
284
+ found: true,
285
+ };
286
+ }
287
+ /**
288
+ * Check if an issue should skip scope assessment (trivial issue)
289
+ *
290
+ * @param acCount - Number of acceptance criteria
291
+ * @param directorySpread - Estimated directory spread
292
+ * @param config - Scope assessment configuration
293
+ * @returns Whether to skip assessment
294
+ */
295
+ export function shouldSkipAssessment(acCount, directorySpread, config = DEFAULT_SCOPE_CONFIG) {
296
+ if (!config.enabled) {
297
+ return { skip: true, reason: "Scope assessment disabled in config" };
298
+ }
299
+ if (!config.skipIfSimple) {
300
+ return { skip: false };
301
+ }
302
+ const { maxACItems, maxDirectories } = config.trivialThresholds;
303
+ if (acCount <= maxACItems && directorySpread <= maxDirectories) {
304
+ return {
305
+ skip: true,
306
+ reason: `Trivial issue (${acCount} AC items, ${directorySpread} directories)`,
307
+ };
308
+ }
309
+ return { skip: false };
310
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Scope Assessment Formatter
3
+ *
4
+ * Formats scope assessment results as markdown for /spec output
5
+ * and GitHub issue comments.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { formatScopeAssessment } from './formatter';
10
+ *
11
+ * const assessment = performScopeAssessment(criteria, issueBody, title);
12
+ * console.log(formatScopeAssessment(assessment));
13
+ * ```
14
+ */
15
+ import type { ScopeAssessment, NonGoals } from "./types.js";
16
+ /**
17
+ * Format non-goals section for output
18
+ *
19
+ * @param nonGoals - Non-goals parsing result
20
+ * @returns Formatted markdown string
21
+ */
22
+ export declare function formatNonGoals(nonGoals: NonGoals): string;
23
+ /**
24
+ * Format scope metrics table
25
+ *
26
+ * @param assessment - Complete scope assessment
27
+ * @returns Formatted markdown table
28
+ */
29
+ export declare function formatMetricsTable(assessment: ScopeAssessment): string;
30
+ /**
31
+ * Format scope verdict section
32
+ *
33
+ * @param assessment - Complete scope assessment
34
+ * @returns Formatted markdown string
35
+ */
36
+ export declare function formatVerdict(assessment: ScopeAssessment): string;
37
+ /**
38
+ * Format complete scope assessment section
39
+ *
40
+ * @param assessment - Complete scope assessment
41
+ * @returns Formatted markdown string
42
+ */
43
+ export declare function formatScopeAssessment(assessment: ScopeAssessment): string;
44
+ /**
45
+ * Format condensed scope assessment for issue comments
46
+ *
47
+ * A shorter version suitable for GitHub issue comments.
48
+ *
49
+ * @param assessment - Complete scope assessment
50
+ * @returns Condensed markdown string
51
+ */
52
+ export declare function formatCondensedAssessment(assessment: ScopeAssessment): string;