sequant 1.12.0 → 1.13.1

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 (53) hide show
  1. package/README.md +10 -8
  2. package/dist/bin/cli.js +19 -9
  3. package/dist/src/commands/doctor.js +42 -20
  4. package/dist/src/commands/init.js +152 -65
  5. package/dist/src/commands/logs.js +7 -6
  6. package/dist/src/commands/run.d.ts +13 -1
  7. package/dist/src/commands/run.js +122 -32
  8. package/dist/src/commands/stats.js +67 -48
  9. package/dist/src/commands/status.js +30 -12
  10. package/dist/src/commands/sync.d.ts +28 -0
  11. package/dist/src/commands/sync.js +102 -0
  12. package/dist/src/index.d.ts +6 -0
  13. package/dist/src/index.js +4 -0
  14. package/dist/src/lib/cli-ui.d.ts +196 -0
  15. package/dist/src/lib/cli-ui.js +544 -0
  16. package/dist/src/lib/content-analyzer.d.ts +89 -0
  17. package/dist/src/lib/content-analyzer.js +437 -0
  18. package/dist/src/lib/phase-signal.d.ts +94 -0
  19. package/dist/src/lib/phase-signal.js +171 -0
  20. package/dist/src/lib/phase-spinner.d.ts +146 -0
  21. package/dist/src/lib/phase-spinner.js +255 -0
  22. package/dist/src/lib/solve-comment-parser.d.ts +84 -0
  23. package/dist/src/lib/solve-comment-parser.js +200 -0
  24. package/dist/src/lib/stack-config.d.ts +51 -0
  25. package/dist/src/lib/stack-config.js +77 -0
  26. package/dist/src/lib/stacks.d.ts +52 -0
  27. package/dist/src/lib/stacks.js +173 -0
  28. package/dist/src/lib/templates.d.ts +2 -0
  29. package/dist/src/lib/templates.js +9 -2
  30. package/dist/src/lib/upstream/assessment.d.ts +70 -0
  31. package/dist/src/lib/upstream/assessment.js +385 -0
  32. package/dist/src/lib/upstream/index.d.ts +11 -0
  33. package/dist/src/lib/upstream/index.js +14 -0
  34. package/dist/src/lib/upstream/issues.d.ts +38 -0
  35. package/dist/src/lib/upstream/issues.js +267 -0
  36. package/dist/src/lib/upstream/relevance.d.ts +50 -0
  37. package/dist/src/lib/upstream/relevance.js +209 -0
  38. package/dist/src/lib/upstream/report.d.ts +29 -0
  39. package/dist/src/lib/upstream/report.js +391 -0
  40. package/dist/src/lib/upstream/types.d.ts +207 -0
  41. package/dist/src/lib/upstream/types.js +5 -0
  42. package/dist/src/lib/workflow/log-writer.d.ts +1 -1
  43. package/dist/src/lib/workflow/metrics-schema.d.ts +3 -3
  44. package/dist/src/lib/workflow/qa-cache.d.ts +199 -0
  45. package/dist/src/lib/workflow/qa-cache.js +440 -0
  46. package/dist/src/lib/workflow/run-log-schema.d.ts +34 -6
  47. package/dist/src/lib/workflow/run-log-schema.js +12 -1
  48. package/dist/src/lib/workflow/state-schema.d.ts +4 -4
  49. package/dist/src/lib/workflow/types.d.ts +4 -0
  50. package/package.json +6 -1
  51. package/templates/skills/qa/scripts/quality-checks.sh +509 -53
  52. package/templates/skills/solve/SKILL.md +375 -83
  53. package/templates/skills/spec/SKILL.md +107 -5
@@ -0,0 +1,385 @@
1
+ /**
2
+ * Main assessment module for upstream analysis
3
+ * Coordinates release fetching, analysis, and output generation
4
+ */
5
+ import { spawn } from "node:child_process";
6
+ import { readFile, writeFile, access, mkdir } from "node:fs/promises";
7
+ import { dirname, join } from "node:path";
8
+ import { analyzeRelease, getActionableFindings } from "./relevance.js";
9
+ import { calculateSummary, generateAssessmentReport, generateBatchedSummaryReport, generateLocalReport, } from "./report.js";
10
+ import { createAssessmentIssue, createOrLinkFinding } from "./issues.js";
11
+ /**
12
+ * Regex pattern for valid semantic version strings
13
+ * Matches: v1.2.3, 1.2.3, v1.2.3-beta.1, v1.2.3-rc1, etc.
14
+ */
15
+ const VERSION_PATTERN = /^v?\d+\.\d+\.\d+(-[\w.]+)?$/;
16
+ /**
17
+ * Validate a version string to prevent command injection
18
+ * @throws Error if version format is invalid
19
+ */
20
+ export function validateVersion(version) {
21
+ if (!VERSION_PATTERN.test(version)) {
22
+ throw new Error(`Invalid version format: "${version}". Expected semver format (e.g., v1.2.3 or 1.2.3-beta.1)`);
23
+ }
24
+ }
25
+ /**
26
+ * Execute a command safely using spawn with argument arrays
27
+ * This prevents command injection by not using shell interpolation
28
+ */
29
+ async function execCommand(command, args) {
30
+ return new Promise((resolve, reject) => {
31
+ const proc = spawn(command, args, { stdio: ["pipe", "pipe", "pipe"] });
32
+ let stdout = "";
33
+ let stderr = "";
34
+ proc.stdout.on("data", (data) => {
35
+ stdout += data.toString();
36
+ });
37
+ proc.stderr.on("data", (data) => {
38
+ stderr += data.toString();
39
+ });
40
+ proc.on("close", (code) => {
41
+ if (code === 0) {
42
+ resolve({ stdout, stderr });
43
+ }
44
+ else {
45
+ reject(new Error(`Command failed with exit code ${code}: ${stderr}`));
46
+ }
47
+ });
48
+ proc.on("error", (err) => {
49
+ reject(err);
50
+ });
51
+ });
52
+ }
53
+ /**
54
+ * Check if gh CLI is available and authenticated
55
+ * @returns Object with availability status and error message if not available
56
+ */
57
+ export async function checkGhCliAvailable() {
58
+ try {
59
+ // Check if gh is installed
60
+ await execCommand("gh", ["--version"]);
61
+ }
62
+ catch {
63
+ return {
64
+ available: false,
65
+ authenticated: false,
66
+ error: "GitHub CLI (gh) is not installed. Install from: https://cli.github.com/",
67
+ };
68
+ }
69
+ try {
70
+ // Check if gh is authenticated
71
+ await execCommand("gh", ["auth", "status"]);
72
+ return { available: true, authenticated: true };
73
+ }
74
+ catch {
75
+ return {
76
+ available: true,
77
+ authenticated: false,
78
+ error: "GitHub CLI is not authenticated. Run: gh auth login",
79
+ };
80
+ }
81
+ }
82
+ /**
83
+ * Default paths for upstream files
84
+ */
85
+ const BASELINE_PATH = ".sequant/upstream/baseline.json";
86
+ const REPORTS_DIR = ".sequant/upstream";
87
+ /**
88
+ * Fetch release data from Claude Code repository
89
+ */
90
+ export async function fetchRelease(version) {
91
+ try {
92
+ // Validate version if provided to prevent injection
93
+ if (version) {
94
+ validateVersion(version);
95
+ }
96
+ // Build args array safely - no shell interpolation
97
+ const args = ["release", "view"];
98
+ if (version) {
99
+ args.push(version);
100
+ }
101
+ args.push("--repo", "anthropics/claude-code", "--json", "tagName,name,body,publishedAt");
102
+ const { stdout } = await execCommand("gh", args);
103
+ return JSON.parse(stdout);
104
+ }
105
+ catch (error) {
106
+ console.error("Error fetching release:", error);
107
+ return null;
108
+ }
109
+ }
110
+ /**
111
+ * List releases from Claude Code repository
112
+ */
113
+ export async function listReleases(limit = 50) {
114
+ try {
115
+ // Validate limit is a reasonable positive integer
116
+ if (!Number.isInteger(limit) || limit < 1 || limit > 100) {
117
+ throw new Error("Limit must be an integer between 1 and 100");
118
+ }
119
+ const { stdout } = await execCommand("gh", [
120
+ "release",
121
+ "list",
122
+ "--repo",
123
+ "anthropics/claude-code",
124
+ "--limit",
125
+ String(limit),
126
+ "--json",
127
+ "tagName,publishedAt",
128
+ ]);
129
+ return JSON.parse(stdout);
130
+ }
131
+ catch (error) {
132
+ console.error("Error listing releases:", error);
133
+ return [];
134
+ }
135
+ }
136
+ /**
137
+ * Get releases since a specific version
138
+ */
139
+ export async function getReleasesSince(sinceVersion) {
140
+ const releases = await listReleases();
141
+ const versions = [];
142
+ for (const release of releases) {
143
+ if (release.tagName === sinceVersion) {
144
+ break;
145
+ }
146
+ versions.push(release.tagName);
147
+ }
148
+ return versions.reverse(); // Oldest first
149
+ }
150
+ /**
151
+ * Load baseline from file
152
+ */
153
+ export async function loadBaseline(path = BASELINE_PATH) {
154
+ try {
155
+ const content = await readFile(path, "utf-8");
156
+ return JSON.parse(content);
157
+ }
158
+ catch {
159
+ // Return default baseline if file doesn't exist
160
+ console.warn("Baseline not found, using defaults");
161
+ return getDefaultBaseline();
162
+ }
163
+ }
164
+ /**
165
+ * Save baseline to file
166
+ */
167
+ export async function saveBaseline(baseline, path = BASELINE_PATH) {
168
+ await ensureDir(dirname(path));
169
+ await writeFile(path, JSON.stringify(baseline, null, 2));
170
+ }
171
+ /**
172
+ * Update baseline with new assessed version
173
+ */
174
+ export async function updateBaseline(version, path = BASELINE_PATH) {
175
+ const baseline = await loadBaseline(path);
176
+ baseline.lastAssessedVersion = version;
177
+ await saveBaseline(baseline, path);
178
+ }
179
+ /**
180
+ * Check if a version has already been assessed
181
+ */
182
+ export async function isAlreadyAssessed(version) {
183
+ const reportPath = join(REPORTS_DIR, `${version}.md`);
184
+ try {
185
+ await access(reportPath);
186
+ return true;
187
+ }
188
+ catch {
189
+ return false;
190
+ }
191
+ }
192
+ /**
193
+ * Save local report
194
+ */
195
+ export async function saveLocalReport(assessment) {
196
+ const reportPath = join(REPORTS_DIR, `${assessment.version}.md`);
197
+ await ensureDir(REPORTS_DIR);
198
+ const content = generateLocalReport(assessment);
199
+ await writeFile(reportPath, content);
200
+ return reportPath;
201
+ }
202
+ /**
203
+ * Run a single version assessment
204
+ */
205
+ export async function assessVersion(version, options = {}) {
206
+ const { dryRun = false, force = false } = options;
207
+ // Check if already assessed
208
+ if (!force && (await isAlreadyAssessed(version))) {
209
+ console.log(`Already assessed: ${version}. Use --force to re-assess.`);
210
+ return null;
211
+ }
212
+ // Fetch release
213
+ const release = await fetchRelease(version);
214
+ if (!release) {
215
+ console.error(`Failed to fetch release: ${version}`);
216
+ return null;
217
+ }
218
+ // Load baseline
219
+ const baseline = await loadBaseline();
220
+ // Analyze
221
+ const findings = analyzeRelease(release.body, baseline);
222
+ const actionableFindings = getActionableFindings(findings);
223
+ // Create assessment object
224
+ const assessment = {
225
+ version: release.tagName,
226
+ releaseDate: release.publishedAt.split("T")[0],
227
+ assessmentDate: new Date().toISOString().split("T")[0],
228
+ previousVersion: baseline.lastAssessedVersion,
229
+ findings,
230
+ issuesCreated: [],
231
+ summary: calculateSummary(findings),
232
+ dryRun,
233
+ };
234
+ // Create assessment issue first (to get issue number for linking)
235
+ const assessmentBody = generateAssessmentReport(assessment);
236
+ const assessmentIssueNumber = await createAssessmentIssue(`Upstream: Claude Code ${release.tagName} Assessment`, assessmentBody, dryRun);
237
+ // Create issues for actionable findings
238
+ for (let i = 0; i < actionableFindings.length; i++) {
239
+ const updatedFinding = await createOrLinkFinding(actionableFindings[i], release.tagName, assessmentIssueNumber, dryRun);
240
+ // Update in original findings array
241
+ const originalIndex = findings.findIndex((f) => f.description === updatedFinding.description);
242
+ if (originalIndex >= 0) {
243
+ findings[originalIndex] = updatedFinding;
244
+ }
245
+ if (updatedFinding.issueNumber) {
246
+ assessment.issuesCreated.push(updatedFinding.issueNumber);
247
+ }
248
+ }
249
+ // Save local report
250
+ await saveLocalReport(assessment);
251
+ // Update baseline
252
+ if (!dryRun) {
253
+ await updateBaseline(release.tagName);
254
+ }
255
+ return assessment;
256
+ }
257
+ /**
258
+ * Run assessment for latest release
259
+ */
260
+ export async function assessLatest(options = {}) {
261
+ const release = await fetchRelease();
262
+ if (!release) {
263
+ console.error("Failed to fetch latest release");
264
+ return null;
265
+ }
266
+ return assessVersion(release.tagName, options);
267
+ }
268
+ /**
269
+ * Run batched assessment for multiple versions
270
+ */
271
+ export async function assessSince(sinceVersion, options = {}) {
272
+ const { dryRun = false } = options;
273
+ const versions = await getReleasesSince(sinceVersion);
274
+ if (versions.length === 0) {
275
+ console.log(`No new versions since ${sinceVersion}`);
276
+ return null;
277
+ }
278
+ console.log(`Found ${versions.length} versions to assess: ${versions.join(", ")}`);
279
+ const assessments = [];
280
+ for (const version of versions) {
281
+ const assessment = await assessVersion(version, {
282
+ ...options,
283
+ force: true,
284
+ });
285
+ if (assessment) {
286
+ assessments.push(assessment);
287
+ }
288
+ }
289
+ if (assessments.length === 0) {
290
+ return null;
291
+ }
292
+ const batched = {
293
+ versions,
294
+ assessments,
295
+ sinceVersion,
296
+ toVersion: versions[versions.length - 1],
297
+ };
298
+ // Create summary issue
299
+ if (!dryRun) {
300
+ const summaryBody = generateBatchedSummaryReport(batched);
301
+ const summaryIssue = await createAssessmentIssue(`Upstream: Claude Code Assessment (${sinceVersion} → ${batched.toVersion})`, summaryBody, dryRun);
302
+ batched.summaryIssueNumber = summaryIssue;
303
+ }
304
+ return batched;
305
+ }
306
+ /**
307
+ * Main entry point for upstream assessment
308
+ */
309
+ export async function runUpstream(options = {}) {
310
+ const { version, since } = options;
311
+ if (since) {
312
+ return assessSince(since, options);
313
+ }
314
+ if (version) {
315
+ return assessVersion(version, options);
316
+ }
317
+ return assessLatest(options);
318
+ }
319
+ /**
320
+ * Get default baseline when none exists
321
+ */
322
+ function getDefaultBaseline() {
323
+ return {
324
+ lastAssessedVersion: null,
325
+ schemaVersion: "1.0.0",
326
+ tools: {
327
+ core: [
328
+ "Task",
329
+ "Bash",
330
+ "Read",
331
+ "Write",
332
+ "Edit",
333
+ "Glob",
334
+ "Grep",
335
+ "TodoWrite",
336
+ ],
337
+ optional: ["WebFetch", "WebSearch", "NotebookEdit", "AskUserQuestion"],
338
+ },
339
+ hooks: {
340
+ used: ["PreToolUse"],
341
+ files: ["src/hooks/pre-tool-hook.ts"],
342
+ },
343
+ mcpServers: {
344
+ required: [],
345
+ optional: ["chrome-devtools", "context7", "sequential-thinking"],
346
+ },
347
+ permissions: {
348
+ patterns: ["Bash(*)", "Task(*)", "Edit(*)"],
349
+ files: [".claude/settings.json"],
350
+ },
351
+ keywords: [
352
+ "Task",
353
+ "Bash",
354
+ "hook",
355
+ "PreToolUse",
356
+ "PostToolUse",
357
+ "MCP",
358
+ "permission",
359
+ "allow",
360
+ "deny",
361
+ "tool",
362
+ "background",
363
+ "parallel",
364
+ "agent",
365
+ "subagent",
366
+ ],
367
+ dependencyMap: {
368
+ permission: [".claude/settings.json"],
369
+ hook: ["src/hooks/pre-tool-hook.ts"],
370
+ Task: [".claude/skills/**/*.md"],
371
+ MCP: [".claude/settings.json"],
372
+ },
373
+ };
374
+ }
375
+ /**
376
+ * Ensure directory exists
377
+ */
378
+ async function ensureDir(dir) {
379
+ try {
380
+ await mkdir(dir, { recursive: true });
381
+ }
382
+ catch {
383
+ // Directory may already exist
384
+ }
385
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Upstream Assessment Module
3
+ *
4
+ * Monitors Claude Code releases and assesses compatibility with sequant.
5
+ * Auto-creates GitHub issues for feature opportunities and breaking changes.
6
+ */
7
+ export { runUpstream, assessVersion, assessLatest, assessSince, fetchRelease, listReleases, getReleasesSince, loadBaseline, saveBaseline, updateBaseline, isAlreadyAssessed, saveLocalReport, validateVersion, checkGhCliAvailable, } from "./assessment.js";
8
+ export { extractChanges, matchKeywords, matchPatterns, categorizeChange, determineImpact, getImpactFiles, generateTitle, analyzeChange, analyzeRelease, getActionableFindings, DEFAULT_PATTERNS, } from "./relevance.js";
9
+ export { calculateSummary, generateAssessmentReport, generateFindingIssue, generateBatchedSummaryReport, generateLocalReport, } from "./report.js";
10
+ export { checkForDuplicate, createIssue, addIssueComment, createOrLinkFinding, createAssessmentIssue, extractSearchTerms, isSimilarTitle, } from "./issues.js";
11
+ export type { FindingCategory, ImpactLevel, Finding, AssessmentSummary, UpstreamAssessment, ReleaseData, ToolsConfig, HooksConfig, McpServersConfig, PermissionsConfig, Baseline, DetectionPatterns, AssessmentOptions, DuplicateCheckResult, IssueParams, IssueResult, BatchedAssessment, } from "./types.js";
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Upstream Assessment Module
3
+ *
4
+ * Monitors Claude Code releases and assesses compatibility with sequant.
5
+ * Auto-creates GitHub issues for feature opportunities and breaking changes.
6
+ */
7
+ // Main exports
8
+ export { runUpstream, assessVersion, assessLatest, assessSince, fetchRelease, listReleases, getReleasesSince, loadBaseline, saveBaseline, updateBaseline, isAlreadyAssessed, saveLocalReport, validateVersion, checkGhCliAvailable, } from "./assessment.js";
9
+ // Relevance detection
10
+ export { extractChanges, matchKeywords, matchPatterns, categorizeChange, determineImpact, getImpactFiles, generateTitle, analyzeChange, analyzeRelease, getActionableFindings, DEFAULT_PATTERNS, } from "./relevance.js";
11
+ // Report generation
12
+ export { calculateSummary, generateAssessmentReport, generateFindingIssue, generateBatchedSummaryReport, generateLocalReport, } from "./report.js";
13
+ // Issue management
14
+ export { checkForDuplicate, createIssue, addIssueComment, createOrLinkFinding, createAssessmentIssue, extractSearchTerms, isSimilarTitle, } from "./issues.js";
@@ -0,0 +1,38 @@
1
+ /**
2
+ * GitHub issue management for upstream assessments
3
+ * Handles issue creation, deduplication, and commenting
4
+ *
5
+ * Security: All gh CLI calls use spawn() with argument arrays to prevent
6
+ * command injection. No shell interpolation is used.
7
+ */
8
+ import type { DuplicateCheckResult, Finding, IssueParams, IssueResult } from "./types.js";
9
+ /**
10
+ * Check if a similar upstream issue already exists
11
+ */
12
+ export declare function checkForDuplicate(title: string, owner?: string, repo?: string): Promise<DuplicateCheckResult>;
13
+ /**
14
+ * Extract search terms from a title
15
+ * Removes common words and version info
16
+ */
17
+ export declare function extractSearchTerms(title: string): string;
18
+ /**
19
+ * Check if two titles are similar enough to be duplicates
20
+ */
21
+ export declare function isSimilarTitle(title1: string, title2: string): boolean;
22
+ /**
23
+ * Create a GitHub issue using a temporary file for the body
24
+ * This avoids any shell escaping issues with complex markdown content
25
+ */
26
+ export declare function createIssue(params: IssueParams, owner?: string, repo?: string): Promise<IssueResult>;
27
+ /**
28
+ * Add a comment to an existing issue
29
+ */
30
+ export declare function addIssueComment(issueNumber: number, comment: string, owner?: string, repo?: string): Promise<void>;
31
+ /**
32
+ * Create or link an issue for a finding
33
+ */
34
+ export declare function createOrLinkFinding(finding: Finding, version: string, assessmentIssueNumber: number | undefined, dryRun?: boolean, owner?: string, repo?: string): Promise<Finding>;
35
+ /**
36
+ * Create the assessment summary issue
37
+ */
38
+ export declare function createAssessmentIssue(title: string, body: string, dryRun?: boolean, owner?: string, repo?: string): Promise<number | undefined>;