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,51 @@
1
+ /**
2
+ * Stack configuration persistence
3
+ *
4
+ * Manages user's stack configuration in .sequant/stack.json
5
+ */
6
+ /**
7
+ * Stack entry with optional path for subdirectory stacks
8
+ */
9
+ export interface StackEntry {
10
+ name: string;
11
+ path?: string;
12
+ }
13
+ /**
14
+ * Persisted stack configuration
15
+ */
16
+ export interface StackConfigFile {
17
+ /** Primary stack (determines dev URL and main commands) */
18
+ primary: StackEntry;
19
+ /** Additional stacks to include in constitution notes */
20
+ additional?: StackEntry[];
21
+ /** When this configuration was last updated */
22
+ lastUpdated?: string;
23
+ }
24
+ /**
25
+ * Load stack configuration from .sequant/stack.json
26
+ *
27
+ * @returns Stack configuration or null if not found
28
+ */
29
+ export declare function loadStackConfig(): Promise<StackConfigFile | null>;
30
+ /**
31
+ * Save stack configuration to .sequant/stack.json
32
+ *
33
+ * @param config - Stack configuration to save
34
+ */
35
+ export declare function saveStackConfig(config: StackConfigFile): Promise<void>;
36
+ /**
37
+ * Check if stack configuration exists
38
+ */
39
+ export declare function hasStackConfig(): Promise<boolean>;
40
+ /**
41
+ * Get the primary stack name from configuration
42
+ *
43
+ * @returns Primary stack name or null if not configured
44
+ */
45
+ export declare function getPrimaryStack(): Promise<string | null>;
46
+ /**
47
+ * Get all configured stacks (primary + additional)
48
+ *
49
+ * @returns Array of all stack names
50
+ */
51
+ export declare function getAllConfiguredStacks(): Promise<string[]>;
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Stack configuration persistence
3
+ *
4
+ * Manages user's stack configuration in .sequant/stack.json
5
+ */
6
+ import { fileExists, readFile, writeFile, ensureDir } from "./fs.js";
7
+ import { dirname } from "path";
8
+ const STACK_CONFIG_PATH = ".sequant/stack.json";
9
+ /**
10
+ * Load stack configuration from .sequant/stack.json
11
+ *
12
+ * @returns Stack configuration or null if not found
13
+ */
14
+ export async function loadStackConfig() {
15
+ try {
16
+ if (!(await fileExists(STACK_CONFIG_PATH))) {
17
+ return null;
18
+ }
19
+ const content = await readFile(STACK_CONFIG_PATH);
20
+ const config = JSON.parse(content);
21
+ // Validate required fields
22
+ if (!config.primary || !config.primary.name) {
23
+ return null;
24
+ }
25
+ return config;
26
+ }
27
+ catch {
28
+ return null;
29
+ }
30
+ }
31
+ /**
32
+ * Save stack configuration to .sequant/stack.json
33
+ *
34
+ * @param config - Stack configuration to save
35
+ */
36
+ export async function saveStackConfig(config) {
37
+ const configWithTimestamp = {
38
+ ...config,
39
+ lastUpdated: new Date().toISOString(),
40
+ };
41
+ await ensureDir(dirname(STACK_CONFIG_PATH));
42
+ await writeFile(STACK_CONFIG_PATH, JSON.stringify(configWithTimestamp, null, 2) + "\n");
43
+ }
44
+ /**
45
+ * Check if stack configuration exists
46
+ */
47
+ export async function hasStackConfig() {
48
+ return fileExists(STACK_CONFIG_PATH);
49
+ }
50
+ /**
51
+ * Get the primary stack name from configuration
52
+ *
53
+ * @returns Primary stack name or null if not configured
54
+ */
55
+ export async function getPrimaryStack() {
56
+ const config = await loadStackConfig();
57
+ return config?.primary?.name ?? null;
58
+ }
59
+ /**
60
+ * Get all configured stacks (primary + additional)
61
+ *
62
+ * @returns Array of all stack names
63
+ */
64
+ export async function getAllConfiguredStacks() {
65
+ const config = await loadStackConfig();
66
+ if (!config)
67
+ return [];
68
+ const stacks = [config.primary.name];
69
+ if (config.additional) {
70
+ for (const entry of config.additional) {
71
+ if (!stacks.includes(entry.name)) {
72
+ stacks.push(entry.name);
73
+ }
74
+ }
75
+ }
76
+ return stacks;
77
+ }
@@ -1,6 +1,30 @@
1
1
  /**
2
2
  * Stack detection and configuration
3
3
  */
4
+ /**
5
+ * Detected stack with location information
6
+ */
7
+ export interface DetectedStack {
8
+ /** Stack name (e.g., "nextjs", "python") */
9
+ stack: string;
10
+ /** Path relative to project root (empty string for root) */
11
+ path: string;
12
+ }
13
+ /**
14
+ * Stack configuration for persistence in .sequant/stack.json
15
+ */
16
+ export interface StackConfig_Persisted {
17
+ /** Primary stack for the project (determines dev URL, commands) */
18
+ primary: {
19
+ name: string;
20
+ path?: string;
21
+ };
22
+ /** Additional stacks to include in constitution notes */
23
+ additional?: Array<{
24
+ name: string;
25
+ path?: string;
26
+ }>;
27
+ }
4
28
  /**
5
29
  * Supported package managers
6
30
  */
@@ -46,6 +70,23 @@ export interface StackConfig {
46
70
  }
47
71
  export declare const STACKS: Record<string, StackConfig>;
48
72
  export declare function detectStack(): Promise<string | null>;
73
+ /**
74
+ * Detect stack in a specific directory
75
+ * Similar to detectStack but operates on a given path
76
+ *
77
+ * @param basePath - Directory path to check (relative to cwd)
78
+ * @returns Stack name or null if not detected
79
+ */
80
+ export declare function detectStackInDirectory(basePath: string): Promise<string | null>;
81
+ /**
82
+ * Detect all stacks in the repository
83
+ *
84
+ * Traverses root and immediate subdirectories (1 level deep) to find
85
+ * all stacks present in a monorepo or multi-stack project.
86
+ *
87
+ * @returns Array of detected stacks with their paths
88
+ */
89
+ export declare function detectAllStacks(): Promise<DetectedStack[]>;
49
90
  export declare function getStackConfig(stack: string): StackConfig;
50
91
  /**
51
92
  * Stack-specific notes for constitution templates
@@ -61,3 +102,14 @@ export declare const STACK_NOTES: Record<string, string>;
61
102
  * @returns The stack-specific notes markdown content
62
103
  */
63
104
  export declare function getStackNotes(stack: string): string;
105
+ /**
106
+ * Get combined stack notes for multiple stacks
107
+ *
108
+ * Combines notes from a primary stack and optional additional stacks
109
+ * into a single markdown section for multi-stack projects.
110
+ *
111
+ * @param primary - Primary stack name (determines primary tooling)
112
+ * @param additional - Additional stacks to include notes for
113
+ * @returns Combined stack notes markdown content
114
+ */
115
+ export declare function getMultiStackNotes(primary: string, additional?: string[]): string;
@@ -1,7 +1,25 @@
1
1
  /**
2
2
  * Stack detection and configuration
3
3
  */
4
+ import { readdir } from "fs/promises";
4
5
  import { fileExists, readFile } from "./fs.js";
6
+ /**
7
+ * Directories to skip during multi-stack detection
8
+ */
9
+ const SKIP_DIRECTORIES = [
10
+ "node_modules",
11
+ ".git",
12
+ "vendor",
13
+ "dist",
14
+ "build",
15
+ ".next",
16
+ ".nuxt",
17
+ ".output",
18
+ "__pycache__",
19
+ "target",
20
+ ".claude",
21
+ ".sequant",
22
+ ];
5
23
  /**
6
24
  * Package manager configurations
7
25
  */
@@ -307,6 +325,128 @@ export async function detectStack() {
307
325
  }
308
326
  return null;
309
327
  }
328
+ /**
329
+ * Detect stack in a specific directory
330
+ * Similar to detectStack but operates on a given path
331
+ *
332
+ * @param basePath - Directory path to check (relative to cwd)
333
+ * @returns Stack name or null if not detected
334
+ */
335
+ export async function detectStackInDirectory(basePath) {
336
+ const pathPrefix = basePath ? `${basePath}/` : "";
337
+ // Check for Next.js config files
338
+ for (const file of STACKS.nextjs.detection.files || []) {
339
+ if (await fileExists(`${pathPrefix}${file}`)) {
340
+ return "nextjs";
341
+ }
342
+ }
343
+ // Check for Astro config files
344
+ for (const file of STACKS.astro.detection.files || []) {
345
+ if (await fileExists(`${pathPrefix}${file}`)) {
346
+ return "astro";
347
+ }
348
+ }
349
+ // Check for SvelteKit config files
350
+ for (const file of STACKS.sveltekit.detection.files || []) {
351
+ if (await fileExists(`${pathPrefix}${file}`)) {
352
+ return "sveltekit";
353
+ }
354
+ }
355
+ // Check for Remix config files
356
+ for (const file of STACKS.remix.detection.files || []) {
357
+ if (await fileExists(`${pathPrefix}${file}`)) {
358
+ return "remix";
359
+ }
360
+ }
361
+ // Check for Nuxt config files
362
+ for (const file of STACKS.nuxt.detection.files || []) {
363
+ if (await fileExists(`${pathPrefix}${file}`)) {
364
+ return "nuxt";
365
+ }
366
+ }
367
+ // Check package.json for JS framework dependencies
368
+ const packageJsonPath = `${pathPrefix}package.json`;
369
+ if (await fileExists(packageJsonPath)) {
370
+ try {
371
+ const pkg = JSON.parse(await readFile(packageJsonPath));
372
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
373
+ if (deps.next)
374
+ return "nextjs";
375
+ if (deps.astro)
376
+ return "astro";
377
+ if (deps["@sveltejs/kit"])
378
+ return "sveltekit";
379
+ if (deps["@remix-run/react"])
380
+ return "remix";
381
+ if (deps.nuxt)
382
+ return "nuxt";
383
+ }
384
+ catch {
385
+ // Ignore parse errors
386
+ }
387
+ }
388
+ // Check for Rust
389
+ if (await fileExists(`${pathPrefix}Cargo.toml`)) {
390
+ return "rust";
391
+ }
392
+ // Check for Go
393
+ if (await fileExists(`${pathPrefix}go.mod`)) {
394
+ return "go";
395
+ }
396
+ // Check for Python
397
+ for (const file of STACKS.python.detection.files || []) {
398
+ if (await fileExists(`${pathPrefix}${file}`)) {
399
+ return "python";
400
+ }
401
+ }
402
+ return null;
403
+ }
404
+ /**
405
+ * Detect all stacks in the repository
406
+ *
407
+ * Traverses root and immediate subdirectories (1 level deep) to find
408
+ * all stacks present in a monorepo or multi-stack project.
409
+ *
410
+ * @returns Array of detected stacks with their paths
411
+ */
412
+ export async function detectAllStacks() {
413
+ const results = [];
414
+ // Check root directory
415
+ const rootStack = await detectStackInDirectory("");
416
+ if (rootStack) {
417
+ results.push({ stack: rootStack, path: "" });
418
+ }
419
+ // Check immediate subdirectories (1 level deep)
420
+ try {
421
+ const entries = await readdir(".", { withFileTypes: true });
422
+ for (const entry of entries) {
423
+ if (!entry.isDirectory())
424
+ continue;
425
+ if (SKIP_DIRECTORIES.includes(entry.name))
426
+ continue;
427
+ if (entry.name.startsWith("."))
428
+ continue; // Skip hidden directories
429
+ const subdirStack = await detectStackInDirectory(entry.name);
430
+ if (subdirStack) {
431
+ // Skip if same stack as root (likely a false positive)
432
+ // e.g., both root and /packages/web detect "nextjs"
433
+ // We want distinct stacks, not duplicates
434
+ const isDuplicate = results.some((r) => r.stack === subdirStack && r.path === "");
435
+ // Only skip if root has the same stack AND this is a common mono-repo pattern
436
+ // (like packages/, apps/, etc.)
437
+ if (!isDuplicate ||
438
+ entry.name === "frontend" ||
439
+ entry.name === "backend") {
440
+ results.push({ stack: subdirStack, path: entry.name });
441
+ }
442
+ }
443
+ }
444
+ }
445
+ catch {
446
+ // Directory read failed, return what we have
447
+ }
448
+ return results;
449
+ }
310
450
  export function getStackConfig(stack) {
311
451
  return STACKS[stack] || STACKS.generic;
312
452
  }
@@ -469,3 +609,36 @@ export const STACK_NOTES = {
469
609
  export function getStackNotes(stack) {
470
610
  return STACK_NOTES[stack] || STACK_NOTES.generic;
471
611
  }
612
+ /**
613
+ * Get combined stack notes for multiple stacks
614
+ *
615
+ * Combines notes from a primary stack and optional additional stacks
616
+ * into a single markdown section for multi-stack projects.
617
+ *
618
+ * @param primary - Primary stack name (determines primary tooling)
619
+ * @param additional - Additional stacks to include notes for
620
+ * @returns Combined stack notes markdown content
621
+ */
622
+ export function getMultiStackNotes(primary, additional = []) {
623
+ const sections = [];
624
+ // Add primary stack notes (with "Primary" marker)
625
+ const primaryNotes = STACK_NOTES[primary] || STACK_NOTES.generic;
626
+ if (additional.length > 0) {
627
+ // Replace the first line heading to include "(Primary)"
628
+ const modifiedPrimary = primaryNotes.replace(/^### (.+)$/m, "### $1 (Primary)");
629
+ sections.push(modifiedPrimary);
630
+ }
631
+ else {
632
+ sections.push(primaryNotes);
633
+ }
634
+ // Add additional stack notes
635
+ for (const stack of additional) {
636
+ if (stack === primary)
637
+ continue; // Skip if same as primary
638
+ const notes = STACK_NOTES[stack];
639
+ if (notes && notes !== STACK_NOTES.generic) {
640
+ sections.push(notes);
641
+ }
642
+ }
643
+ return sections.join("\n\n---\n\n");
644
+ }
@@ -32,6 +32,8 @@ export interface CopyTemplatesOptions {
32
32
  noSymlinks?: boolean;
33
33
  /** Force replacement of existing files/symlinks */
34
34
  force?: boolean;
35
+ /** Additional stacks to include in constitution notes (for multi-stack projects) */
36
+ additionalStacks?: string[];
35
37
  }
36
38
  /**
37
39
  * Create symlinks for files in a directory, with fallback to copy
@@ -5,7 +5,9 @@ import { readdir, chmod } from "fs/promises";
5
5
  import { join, dirname, relative, isAbsolute } from "path";
6
6
  import { fileURLToPath } from "url";
7
7
  import { readFile, writeFile, ensureDir, fileExists, isSymlink, createSymlink, removeFileOrSymlink, } from "./fs.js";
8
- import { getStackConfig, getStackNotes } from "./stacks.js";
8
+ import { getPackageVersion } from "./manifest.js";
9
+ const SKILLS_VERSION_PATH = ".claude/skills/.sequant-version";
10
+ import { getStackConfig, getStackNotes, getMultiStackNotes } from "./stacks.js";
9
11
  import { isNativeWindows } from "./system.js";
10
12
  import { getProjectName } from "./project-name.js";
11
13
  // Get the package templates directory
@@ -161,7 +163,10 @@ export async function copyTemplates(stack, tokens, options = {}) {
161
163
  // Detect project name from available sources (package.json, Cargo.toml, etc.)
162
164
  const projectName = await getProjectName();
163
165
  // Get stack-specific notes for constitution template
164
- const stackNotes = getStackNotes(stack);
166
+ // Use multi-stack notes if additional stacks are provided
167
+ const stackNotes = options.additionalStacks && options.additionalStacks.length > 0
168
+ ? getMultiStackNotes(stack, options.additionalStacks)
169
+ : getStackNotes(stack);
165
170
  const variables = {
166
171
  ...stackConfig.variables,
167
172
  ...tokens,
@@ -224,5 +229,7 @@ export async function copyTemplates(stack, tokens, options = {}) {
224
229
  const content = await readFile(settingsPath);
225
230
  await writeFile(".claude/settings.json", processTemplate(content, variables));
226
231
  }
232
+ // Write skills version marker for sync detection
233
+ await writeFile(SKILLS_VERSION_PATH, getPackageVersion());
227
234
  return { scriptsSymlinked, symlinkResults };
228
235
  }
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Main assessment module for upstream analysis
3
+ * Coordinates release fetching, analysis, and output generation
4
+ */
5
+ import type { AssessmentOptions, Baseline, BatchedAssessment, ReleaseData, UpstreamAssessment } from "./types.js";
6
+ /**
7
+ * Validate a version string to prevent command injection
8
+ * @throws Error if version format is invalid
9
+ */
10
+ export declare function validateVersion(version: string): void;
11
+ /**
12
+ * Check if gh CLI is available and authenticated
13
+ * @returns Object with availability status and error message if not available
14
+ */
15
+ export declare function checkGhCliAvailable(): Promise<{
16
+ available: boolean;
17
+ authenticated: boolean;
18
+ error?: string;
19
+ }>;
20
+ /**
21
+ * Fetch release data from Claude Code repository
22
+ */
23
+ export declare function fetchRelease(version?: string): Promise<ReleaseData | null>;
24
+ /**
25
+ * List releases from Claude Code repository
26
+ */
27
+ export declare function listReleases(limit?: number): Promise<Array<{
28
+ tagName: string;
29
+ publishedAt: string;
30
+ }>>;
31
+ /**
32
+ * Get releases since a specific version
33
+ */
34
+ export declare function getReleasesSince(sinceVersion: string): Promise<string[]>;
35
+ /**
36
+ * Load baseline from file
37
+ */
38
+ export declare function loadBaseline(path?: string): Promise<Baseline>;
39
+ /**
40
+ * Save baseline to file
41
+ */
42
+ export declare function saveBaseline(baseline: Baseline, path?: string): Promise<void>;
43
+ /**
44
+ * Update baseline with new assessed version
45
+ */
46
+ export declare function updateBaseline(version: string, path?: string): Promise<void>;
47
+ /**
48
+ * Check if a version has already been assessed
49
+ */
50
+ export declare function isAlreadyAssessed(version: string): Promise<boolean>;
51
+ /**
52
+ * Save local report
53
+ */
54
+ export declare function saveLocalReport(assessment: UpstreamAssessment): Promise<string>;
55
+ /**
56
+ * Run a single version assessment
57
+ */
58
+ export declare function assessVersion(version: string, options?: AssessmentOptions): Promise<UpstreamAssessment | null>;
59
+ /**
60
+ * Run assessment for latest release
61
+ */
62
+ export declare function assessLatest(options?: AssessmentOptions): Promise<UpstreamAssessment | null>;
63
+ /**
64
+ * Run batched assessment for multiple versions
65
+ */
66
+ export declare function assessSince(sinceVersion: string, options?: AssessmentOptions): Promise<BatchedAssessment | null>;
67
+ /**
68
+ * Main entry point for upstream assessment
69
+ */
70
+ export declare function runUpstream(options?: AssessmentOptions): Promise<UpstreamAssessment | BatchedAssessment | null>;