sequant 1.5.2 → 1.5.4

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.
package/dist/bin/cli.js CHANGED
@@ -89,6 +89,7 @@ program
89
89
  .option("--smart-tests", "Enable smart test detection (default)")
90
90
  .option("--no-smart-tests", "Disable smart test detection")
91
91
  .option("--testgen", "Run testgen phase after spec")
92
+ .option("--quiet", "Suppress version warnings and non-essential output")
92
93
  .action(runCommand);
93
94
  program
94
95
  .command("logs")
@@ -5,9 +5,39 @@ import chalk from "chalk";
5
5
  import { fileExists, isExecutable } from "../lib/fs.js";
6
6
  import { getManifest } from "../lib/manifest.js";
7
7
  import { commandExists, isGhAuthenticated, isNativeWindows, isWSL, checkOptionalMcpServers, OPTIONAL_MCP_SERVERS, } from "../lib/system.js";
8
+ import { checkVersionThorough, getVersionWarning, } from "../lib/version-check.js";
8
9
  export async function doctorCommand() {
9
10
  console.log(chalk.blue("\nšŸ” Running health checks...\n"));
10
11
  const checks = [];
12
+ // Check 0: Version freshness
13
+ const versionResult = await checkVersionThorough();
14
+ if (versionResult.latestVersion) {
15
+ if (versionResult.isOutdated) {
16
+ checks.push({
17
+ name: "Version",
18
+ status: "warn",
19
+ message: `Outdated: ${versionResult.currentVersion} → ${versionResult.latestVersion} available`,
20
+ });
21
+ // Show remediation steps
22
+ console.log(chalk.yellow(` āš ļø ${getVersionWarning(versionResult.currentVersion, versionResult.latestVersion)}`));
23
+ console.log("");
24
+ }
25
+ else {
26
+ checks.push({
27
+ name: "Version",
28
+ status: "pass",
29
+ message: `Up to date (${versionResult.currentVersion})`,
30
+ });
31
+ }
32
+ }
33
+ else {
34
+ // Could not fetch version - skip this check silently (graceful degradation)
35
+ checks.push({
36
+ name: "Version",
37
+ status: "pass",
38
+ message: `${versionResult.currentVersion} (could not verify latest)`,
39
+ });
40
+ }
11
41
  // Check 1: Manifest exists
12
42
  const manifest = await getManifest();
13
43
  if (manifest) {
@@ -56,6 +56,8 @@ interface RunOptions {
56
56
  worktreeIsolation?: boolean;
57
57
  /** Reuse existing worktrees instead of creating new ones */
58
58
  reuseWorktrees?: boolean;
59
+ /** Suppress version warnings and non-essential output */
60
+ quiet?: boolean;
59
61
  }
60
62
  /**
61
63
  * Main run command
@@ -15,6 +15,7 @@ import { PM_CONFIG } from "../lib/stacks.js";
15
15
  import { LogWriter, createPhaseLogFromTiming, } from "../lib/workflow/log-writer.js";
16
16
  import { DEFAULT_PHASES, DEFAULT_CONFIG, } from "../lib/workflow/types.js";
17
17
  import { ShutdownManager } from "../lib/shutdown.js";
18
+ import { checkVersionCached, getVersionWarning } from "../lib/version-check.js";
18
19
  /**
19
20
  * Slugify a title for branch naming
20
21
  */
@@ -726,6 +727,19 @@ function parseBatches(batchArgs) {
726
727
  */
727
728
  export async function runCommand(issues, options) {
728
729
  console.log(chalk.blue("\n🌐 Sequant Workflow Execution\n"));
730
+ // Version freshness check (cached, non-blocking, respects --quiet)
731
+ if (!options.quiet) {
732
+ try {
733
+ const versionResult = await checkVersionCached();
734
+ if (versionResult.isOutdated && versionResult.latestVersion) {
735
+ console.log(chalk.yellow(` āš ļø ${getVersionWarning(versionResult.currentVersion, versionResult.latestVersion)}`));
736
+ console.log("");
737
+ }
738
+ }
739
+ catch {
740
+ // Silent failure - version check is non-critical
741
+ }
742
+ }
729
743
  // Check if initialized
730
744
  const manifest = await getManifest();
731
745
  if (!manifest) {
@@ -2,13 +2,13 @@
2
2
  * sequant status - Show version and configuration
3
3
  */
4
4
  import chalk from "chalk";
5
- import { getManifest } from "../lib/manifest.js";
5
+ import { getManifest, getPackageVersion } from "../lib/manifest.js";
6
6
  import { fileExists } from "../lib/fs.js";
7
7
  import { readdir } from "fs/promises";
8
8
  export async function statusCommand() {
9
9
  console.log(chalk.bold("\nšŸ“Š Sequant Status\n"));
10
10
  // Package version
11
- console.log(chalk.gray("Package version: 0.1.0"));
11
+ console.log(chalk.gray(`Package version: ${getPackageVersion()}`));
12
12
  // Check initialization
13
13
  const manifest = await getManifest();
14
14
  if (!manifest) {
@@ -5,7 +5,7 @@ import chalk from "chalk";
5
5
  import { diffLines } from "diff";
6
6
  import inquirer from "inquirer";
7
7
  import { spawnSync } from "child_process";
8
- import { getManifest, updateManifest } from "../lib/manifest.js";
8
+ import { getManifest, updateManifest, getPackageVersion, } from "../lib/manifest.js";
9
9
  import { getTemplateContent, listTemplateFiles, processTemplate, } from "../lib/templates.js";
10
10
  import { getConfig, saveConfig } from "../lib/config.js";
11
11
  import { getStackConfig, PM_CONFIG, getPackageManagerCommands, } from "../lib/stacks.js";
@@ -18,8 +18,19 @@ export async function updateCommand(options) {
18
18
  console.log(chalk.red("āŒ Sequant is not initialized. Run `sequant init` first."));
19
19
  return;
20
20
  }
21
+ const packageVersion = getPackageVersion();
21
22
  console.log(chalk.gray(`Current version: ${manifest.version}`));
22
- console.log(chalk.gray(`Stack: ${manifest.stack}\n`));
23
+ console.log(chalk.gray(`Stack: ${manifest.stack}`));
24
+ console.log(chalk.gray(`CLI version: ${packageVersion}\n`));
25
+ // Warn if running an older CLI version than what's installed
26
+ const [mMajor, mMinor, mPatch] = manifest.version.split(".").map(Number);
27
+ const [pMajor, pMinor, pPatch] = packageVersion.split(".").map(Number);
28
+ const manifestNum = mMajor * 10000 + mMinor * 100 + mPatch;
29
+ const packageNum = pMajor * 10000 + pMinor * 100 + pPatch;
30
+ if (packageNum < manifestNum) {
31
+ console.log(chalk.yellow(`āš ļø Warning: You're running an older CLI version (${packageVersion}) than installed (${manifest.version}).`));
32
+ console.log(chalk.yellow(` Run with: npx sequant@latest update\n`));
33
+ }
23
34
  // Get config with tokens (or migrate legacy installs)
24
35
  let config = await getConfig();
25
36
  let tokens;
@@ -2,6 +2,7 @@
2
2
  * Manifest management for tracking installed version
3
3
  */
4
4
  import type { PackageManager } from "./stacks.js";
5
+ export declare function getPackageVersion(): string;
5
6
  export interface Manifest {
6
7
  version: string;
7
8
  stack: string;
@@ -29,6 +29,26 @@ function findPackageJson() {
29
29
  }
30
30
  const pkg = JSON.parse(findPackageJson());
31
31
  const PACKAGE_VERSION = pkg.version;
32
+ export function getPackageVersion() {
33
+ return PACKAGE_VERSION;
34
+ }
35
+ /**
36
+ * Compare two semver versions.
37
+ * Returns: 1 if a > b, -1 if a < b, 0 if equal
38
+ */
39
+ function compareVersions(a, b) {
40
+ const partsA = a.split(".").map(Number);
41
+ const partsB = b.split(".").map(Number);
42
+ for (let i = 0; i < 3; i++) {
43
+ const numA = partsA[i] || 0;
44
+ const numB = partsB[i] || 0;
45
+ if (numA > numB)
46
+ return 1;
47
+ if (numA < numB)
48
+ return -1;
49
+ }
50
+ return 0;
51
+ }
32
52
  export async function getManifest() {
33
53
  if (!(await fileExists(MANIFEST_PATH))) {
34
54
  return null;
@@ -56,7 +76,11 @@ export async function updateManifest() {
56
76
  if (!manifest) {
57
77
  return;
58
78
  }
59
- manifest.version = PACKAGE_VERSION;
79
+ // Only update version if package version is >= manifest version
80
+ // This prevents older cached CLI versions from downgrading the manifest
81
+ if (compareVersions(PACKAGE_VERSION, manifest.version) >= 0) {
82
+ manifest.version = PACKAGE_VERSION;
83
+ }
60
84
  manifest.updatedAt = new Date().toISOString();
61
85
  await writeFile(MANIFEST_PATH, JSON.stringify(manifest, null, 2));
62
86
  }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Version freshness checks for sequant
3
+ *
4
+ * Provides utilities to check if the current version is up to date
5
+ * with the latest version on npm.
6
+ */
7
+ export interface VersionCache {
8
+ latestVersion: string;
9
+ checkedAt: string;
10
+ }
11
+ export interface VersionCheckResult {
12
+ currentVersion: string;
13
+ latestVersion: string | null;
14
+ isOutdated: boolean;
15
+ error?: string;
16
+ }
17
+ /**
18
+ * Get the global cache directory path
19
+ */
20
+ export declare function getCacheDir(): string;
21
+ /**
22
+ * Get the version cache file path
23
+ */
24
+ export declare function getCachePath(): string;
25
+ /**
26
+ * Get the current version from package.json
27
+ */
28
+ export declare function getCurrentVersion(): string;
29
+ /**
30
+ * Read the version cache
31
+ */
32
+ export declare function readCache(): VersionCache | null;
33
+ /**
34
+ * Write to the version cache
35
+ */
36
+ export declare function writeCache(latestVersion: string): void;
37
+ /**
38
+ * Check if the cache is still fresh (within 24 hours)
39
+ */
40
+ export declare function isCacheFresh(cache: VersionCache): boolean;
41
+ /**
42
+ * Fetch the latest version from npm registry with timeout
43
+ */
44
+ export declare function fetchLatestVersion(): Promise<string | null>;
45
+ /**
46
+ * Compare two semantic versions
47
+ * Returns: -1 if a < b, 0 if a == b, 1 if a > b
48
+ */
49
+ export declare function compareVersions(a: string, b: string): number;
50
+ /**
51
+ * Check if the current version is outdated
52
+ */
53
+ export declare function isOutdated(currentVersion: string, latestVersion: string): boolean;
54
+ /**
55
+ * Get the version warning message
56
+ */
57
+ export declare function getVersionWarning(currentVersion: string, latestVersion: string): string;
58
+ /**
59
+ * Check version freshness (thorough - for doctor command)
60
+ * Always fetches from npm registry
61
+ */
62
+ export declare function checkVersionThorough(): Promise<VersionCheckResult>;
63
+ /**
64
+ * Check version freshness (cached - for run command)
65
+ * Uses cache if available and fresh, otherwise fetches (non-blocking)
66
+ */
67
+ export declare function checkVersionCached(): Promise<VersionCheckResult>;
@@ -0,0 +1,215 @@
1
+ /**
2
+ * Version freshness checks for sequant
3
+ *
4
+ * Provides utilities to check if the current version is up to date
5
+ * with the latest version on npm.
6
+ */
7
+ import fs from "fs";
8
+ import path from "path";
9
+ import { fileURLToPath } from "url";
10
+ const __filename = fileURLToPath(import.meta.url);
11
+ const __dirname = path.dirname(__filename);
12
+ const PACKAGE_NAME = "sequant";
13
+ const NPM_REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
14
+ const VERSION_CHECK_TIMEOUT = 3000; // 3 seconds
15
+ const CACHE_EXPIRY_MS = 24 * 60 * 60 * 1000; // 24 hours
16
+ /**
17
+ * Get the global cache directory path
18
+ */
19
+ export function getCacheDir() {
20
+ const home = process.env.HOME || process.env.USERPROFILE || "";
21
+ return path.join(home, ".cache", "sequant");
22
+ }
23
+ /**
24
+ * Get the version cache file path
25
+ */
26
+ export function getCachePath() {
27
+ return path.join(getCacheDir(), "version-check.json");
28
+ }
29
+ /**
30
+ * Get the current version from package.json
31
+ */
32
+ export function getCurrentVersion() {
33
+ try {
34
+ // Navigate from dist/lib to package.json
35
+ const packagePath = path.resolve(__dirname, "..", "..", "package.json");
36
+ const content = fs.readFileSync(packagePath, "utf8");
37
+ const pkg = JSON.parse(content);
38
+ return pkg.version || "0.0.0";
39
+ }
40
+ catch {
41
+ return "0.0.0";
42
+ }
43
+ }
44
+ /**
45
+ * Read the version cache
46
+ */
47
+ export function readCache() {
48
+ try {
49
+ const cachePath = getCachePath();
50
+ if (!fs.existsSync(cachePath)) {
51
+ return null;
52
+ }
53
+ const content = fs.readFileSync(cachePath, "utf8");
54
+ return JSON.parse(content);
55
+ }
56
+ catch {
57
+ return null;
58
+ }
59
+ }
60
+ /**
61
+ * Write to the version cache
62
+ */
63
+ export function writeCache(latestVersion) {
64
+ try {
65
+ const cacheDir = getCacheDir();
66
+ if (!fs.existsSync(cacheDir)) {
67
+ fs.mkdirSync(cacheDir, { recursive: true });
68
+ }
69
+ const cache = {
70
+ latestVersion,
71
+ checkedAt: new Date().toISOString(),
72
+ };
73
+ fs.writeFileSync(getCachePath(), JSON.stringify(cache, null, 2));
74
+ }
75
+ catch {
76
+ // Silent failure - caching is optional
77
+ }
78
+ }
79
+ /**
80
+ * Check if the cache is still fresh (within 24 hours)
81
+ */
82
+ export function isCacheFresh(cache) {
83
+ try {
84
+ const checkedAt = new Date(cache.checkedAt).getTime();
85
+ const now = Date.now();
86
+ return now - checkedAt < CACHE_EXPIRY_MS;
87
+ }
88
+ catch {
89
+ return false;
90
+ }
91
+ }
92
+ /**
93
+ * Fetch the latest version from npm registry with timeout
94
+ */
95
+ export async function fetchLatestVersion() {
96
+ const controller = new AbortController();
97
+ const timeoutId = setTimeout(() => controller.abort(), VERSION_CHECK_TIMEOUT);
98
+ try {
99
+ const response = await fetch(NPM_REGISTRY_URL, {
100
+ signal: controller.signal,
101
+ headers: {
102
+ Accept: "application/json",
103
+ },
104
+ });
105
+ clearTimeout(timeoutId);
106
+ if (!response.ok) {
107
+ return null;
108
+ }
109
+ const data = (await response.json());
110
+ return data.version || null;
111
+ }
112
+ catch {
113
+ clearTimeout(timeoutId);
114
+ return null;
115
+ }
116
+ }
117
+ /**
118
+ * Compare two semantic versions
119
+ * Returns: -1 if a < b, 0 if a == b, 1 if a > b
120
+ */
121
+ export function compareVersions(a, b) {
122
+ const parseVersion = (v) => {
123
+ return v
124
+ .replace(/^v/, "")
125
+ .split(".")
126
+ .map((n) => parseInt(n, 10) || 0);
127
+ };
128
+ const aParts = parseVersion(a);
129
+ const bParts = parseVersion(b);
130
+ for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) {
131
+ const aVal = aParts[i] || 0;
132
+ const bVal = bParts[i] || 0;
133
+ if (aVal < bVal)
134
+ return -1;
135
+ if (aVal > bVal)
136
+ return 1;
137
+ }
138
+ return 0;
139
+ }
140
+ /**
141
+ * Check if the current version is outdated
142
+ */
143
+ export function isOutdated(currentVersion, latestVersion) {
144
+ return compareVersions(currentVersion, latestVersion) < 0;
145
+ }
146
+ /**
147
+ * Get the version warning message
148
+ */
149
+ export function getVersionWarning(currentVersion, latestVersion) {
150
+ return `sequant ${latestVersion} is available (you have ${currentVersion})
151
+ Run: npx sequant@latest or npm update sequant`;
152
+ }
153
+ /**
154
+ * Check version freshness (thorough - for doctor command)
155
+ * Always fetches from npm registry
156
+ */
157
+ export async function checkVersionThorough() {
158
+ const currentVersion = getCurrentVersion();
159
+ const latestVersion = await fetchLatestVersion();
160
+ if (!latestVersion) {
161
+ return {
162
+ currentVersion,
163
+ latestVersion: null,
164
+ isOutdated: false,
165
+ error: "Could not fetch latest version",
166
+ };
167
+ }
168
+ // Update cache with fresh data
169
+ writeCache(latestVersion);
170
+ return {
171
+ currentVersion,
172
+ latestVersion,
173
+ isOutdated: isOutdated(currentVersion, latestVersion),
174
+ };
175
+ }
176
+ /**
177
+ * Check version freshness (cached - for run command)
178
+ * Uses cache if available and fresh, otherwise fetches (non-blocking)
179
+ */
180
+ export async function checkVersionCached() {
181
+ const currentVersion = getCurrentVersion();
182
+ // Check cache first
183
+ const cache = readCache();
184
+ if (cache && isCacheFresh(cache)) {
185
+ return {
186
+ currentVersion,
187
+ latestVersion: cache.latestVersion,
188
+ isOutdated: isOutdated(currentVersion, cache.latestVersion),
189
+ };
190
+ }
191
+ // Fetch new version (with timeout)
192
+ const latestVersion = await fetchLatestVersion();
193
+ if (!latestVersion) {
194
+ // Use stale cache if available, otherwise silent failure
195
+ if (cache) {
196
+ return {
197
+ currentVersion,
198
+ latestVersion: cache.latestVersion,
199
+ isOutdated: isOutdated(currentVersion, cache.latestVersion),
200
+ };
201
+ }
202
+ return {
203
+ currentVersion,
204
+ latestVersion: null,
205
+ isOutdated: false,
206
+ };
207
+ }
208
+ // Update cache
209
+ writeCache(latestVersion);
210
+ return {
211
+ currentVersion,
212
+ latestVersion,
213
+ isOutdated: isOutdated(currentVersion, latestVersion),
214
+ };
215
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sequant",
3
- "version": "1.5.2",
3
+ "version": "1.5.4",
4
4
  "description": "Quantize your development workflow - Sequential AI phases with quality gates",
5
5
  "type": "module",
6
6
  "bin": {
@@ -114,22 +114,17 @@ If no feature worktree exists (work was done directly on main):
114
114
 
115
115
  4. **Run quality checks** on the current branch instead of comparing to a worktree.
116
116
 
117
- ### Parallel Quality Checks (Multi-Agent)
117
+ ### Quality Checks (Multi-Agent) — REQUIRED
118
118
 
119
- Before detailed manual review, run quality checks in parallel using specialized agents.
119
+ **You MUST spawn sub-agents for quality checks.** Do NOT run these checks inline with bash commands. Sub-agents provide parallel execution, better context isolation, and consistent reporting.
120
120
 
121
- **Spawn agents in a SINGLE message:**
121
+ **Spawn ALL THREE agents in a SINGLE message:**
122
122
 
123
- ```
124
- Task(subagent_type="quality-checker", model="haiku",
125
- prompt="Run type safety and deleted tests checks. Report: type issues count, deleted tests, verdict.")
123
+ 1. `Task(subagent_type="quality-checker", model="haiku", prompt="Run type safety and deleted tests checks on the current branch vs main. Report: type issues count, deleted tests, verdict.")`
126
124
 
127
- Task(subagent_type="quality-checker", model="haiku",
128
- prompt="Run scope and size checks. Report: files count, diff size, size assessment.")
125
+ 2. `Task(subagent_type="quality-checker", model="haiku", prompt="Run scope and size checks on the current branch vs main. Report: files count, diff size, size assessment.")`
129
126
 
130
- Task(subagent_type="quality-checker", model="haiku",
131
- prompt="Run security scan on changed files. Report: critical/warning/info counts, verdict.")
132
- ```
127
+ 3. `Task(subagent_type="quality-checker", model="haiku", prompt="Run security scan on changed files in current branch vs main. Report: critical/warning/info counts, verdict.")`
133
128
 
134
129
  **Add RLS check if admin files modified:**
135
130
  ```bash
@@ -191,9 +186,9 @@ Provide an overall verdict:
191
186
 
192
187
  See [quality-gates.md](references/quality-gates.md) for detailed verdict criteria.
193
188
 
194
- ## Automated Quality Checks
189
+ ## Automated Quality Checks (Reference)
195
190
 
196
- Run these before detailed review (or use parallel agents above):
191
+ **Note:** These commands are what the sub-agents execute internally. You do NOT run these directly — the sub-agents spawned above handle this. This section is reference documentation only.
197
192
 
198
193
  ```bash
199
194
  # Type safety
@@ -45,22 +45,17 @@ When called like `/spec <freeform description>`:
45
45
 
46
46
  **Planning Phase:** No worktree needed. Planning happens in the main repository directory. The worktree will be created during the execution phase (`/exec`).
47
47
 
48
- ### Parallel Context Gathering
48
+ ### Parallel Context Gathering — REQUIRED
49
49
 
50
- Before planning, gather context using parallel agents:
50
+ **You MUST spawn sub-agents for context gathering.** Do NOT explore the codebase inline with Glob/Grep commands. Sub-agents provide parallel execution, better context isolation, and consistent reporting.
51
51
 
52
- ```
53
- Task(subagent_type="pattern-scout", model="haiku",
54
- prompt="Find similar features. Check components/admin/, lib/queries/, docs/patterns/. Report: file paths, patterns, recommendations.")
52
+ **Spawn ALL THREE agents in a SINGLE message:**
55
53
 
56
- Task(subagent_type="Explore", model="haiku",
57
- prompt="Explore [CODEBASE AREA]. Find: main components, data flow, key files. Report structure.")
54
+ 1. `Task(subagent_type="pattern-scout", model="haiku", prompt="Find similar features for [FEATURE]. Check components/admin/, lib/queries/, docs/patterns/. Report: file paths, patterns, recommendations.")`
58
55
 
59
- Task(subagent_type="schema-inspector", model="haiku",
60
- prompt="Inspect database for [FEATURE]. Check: table schema, RLS policies, existing queries. Report findings.")
61
- ```
56
+ 2. `Task(subagent_type="Explore", model="haiku", prompt="Explore [CODEBASE AREA] for [FEATURE]. Find: main components, data flow, key files. Report structure.")`
62
57
 
63
- **Important:** Spawn all agents in a SINGLE message for parallel execution.
58
+ 3. `Task(subagent_type="schema-inspector", model="haiku", prompt="Inspect database for [FEATURE]. Check: table schema, RLS policies, existing queries. Report findings.")`
64
59
 
65
60
  ### In-Flight Work Analysis (Conflict Detection)
66
61