@fission-ai/openspec 1.14.3 → 1.15.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.
- package/README.md +13 -9
- package/dist/cli/index.js +47 -0
- package/dist/commands/feedback.js +33 -20
- package/dist/commands/install-standalone.d.ts +27 -0
- package/dist/commands/install-standalone.js +150 -0
- package/dist/commands/schema.js +10 -3
- package/dist/commands/validate.js +10 -3
- package/dist/commands/workflow/instructions.js +15 -4
- package/dist/commands/workflow/new-change.js +12 -4
- package/dist/commands/workflow/status.js +5 -2
- package/dist/commands/workflow/templates.js +5 -2
- package/dist/core/artifact-graph/resolver.d.ts +6 -0
- package/dist/core/artifact-graph/resolver.js +11 -0
- package/dist/core/available-tools.d.ts +2 -1
- package/dist/core/available-tools.js +8 -28
- package/dist/core/completion-tip.d.ts +23 -0
- package/dist/core/completion-tip.js +4 -4
- package/dist/core/completions/command-registry.js +15 -0
- package/dist/core/config-schema.d.ts +1 -0
- package/dist/core/config-schema.js +1 -0
- package/dist/core/file-view.d.ts +13 -0
- package/dist/core/file-view.js +14 -0
- package/dist/core/github-copilot/cloud-agent.d.ts +10 -2
- package/dist/core/github-copilot/cloud-agent.js +13 -9
- package/dist/core/global-config.d.ts +4 -0
- package/dist/core/global-config.js +5 -1
- package/dist/core/legacy-cleanup.d.ts +11 -1
- package/dist/core/legacy-cleanup.js +15 -18
- package/dist/core/migration.d.ts +27 -1
- package/dist/core/migration.js +77 -29
- package/dist/core/profile-sync-drift.d.ts +4 -3
- package/dist/core/profile-sync-drift.js +19 -16
- package/dist/core/shared/tool-detection.d.ts +12 -4
- package/dist/core/shared/tool-detection.js +29 -27
- package/dist/core/shared-skill-target.d.ts +12 -5
- package/dist/core/shared-skill-target.js +34 -23
- package/dist/core/standalone-tip.d.ts +6 -0
- package/dist/core/standalone-tip.js +38 -0
- package/dist/core/templates/workflows/verify-change.js +4 -4
- package/dist/core/update-plan.d.ts +127 -0
- package/dist/core/update-plan.js +390 -0
- package/dist/core/update.d.ts +45 -1
- package/dist/core/update.js +279 -137
- package/dist/core/version-check.d.ts +8 -1
- package/dist/core/version-check.js +48 -5
- package/dist/utils/executable-path.d.ts +5 -0
- package/dist/utils/executable-path.js +44 -0
- package/package.json +4 -3
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Called by the standalone binary's entry point. That copy has no package
|
|
3
|
+
* manager behind it, so a package-manager command is the wrong upgrade advice.
|
|
4
|
+
*/
|
|
5
|
+
export declare function markStandaloneBinary(executablePath: string): void;
|
|
6
|
+
/** The standalone binary's own path, or null when running from a package. */
|
|
7
|
+
export declare function getStandaloneBinaryPath(): string | null;
|
|
1
8
|
/**
|
|
2
9
|
* The registry to ask. A configured registry is honored only over TLS: a
|
|
3
10
|
* cleartext hop would aim the request at a link-local or internal address and
|
|
@@ -79,7 +86,7 @@ export declare function isNpmGlobalInstall(installDir: string | null, roots?: st
|
|
|
79
86
|
*/
|
|
80
87
|
export declare function isSourceCheckout(installDir: string | null): boolean;
|
|
81
88
|
export type PackageManager = 'npm' | 'pnpm' | 'bun' | 'yarn' | 'volta';
|
|
82
|
-
export type InstallScope = 'global' | 'project' | 'temporary' | 'source';
|
|
89
|
+
export type InstallScope = 'global' | 'project' | 'temporary' | 'source' | 'standalone';
|
|
83
90
|
export interface CliInstallInfo {
|
|
84
91
|
location: string | null;
|
|
85
92
|
packageManager: PackageManager | null;
|
|
@@ -3,11 +3,13 @@ import http from 'http';
|
|
|
3
3
|
import https from 'https';
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import { createRequire } from 'module';
|
|
6
|
+
import { fileURLToPath } from 'url';
|
|
6
7
|
import chalk from 'chalk';
|
|
7
8
|
import { isCiEnvironment } from '../utils/ci.js';
|
|
8
9
|
import { isTelemetryOptedOutByEnv } from '../telemetry/opt-out.js';
|
|
9
10
|
import { FileSystemUtils } from '../utils/file-system.js';
|
|
10
11
|
import { getGlobalConfig, isGlobalConfigUnreadable } from './global-config.js';
|
|
12
|
+
import { findCopiesOnPath, samePath } from '../utils/executable-path.js';
|
|
11
13
|
const require = createRequire(import.meta.url);
|
|
12
14
|
const { name: PACKAGE_NAME, version: OPENSPEC_VERSION } = require('../../package.json');
|
|
13
15
|
const DEFAULT_REGISTRY = 'https://registry.npmjs.org';
|
|
@@ -22,6 +24,34 @@ const MAX_REDIRECTS = 3;
|
|
|
22
24
|
* one could smuggle ANSI cursor controls and repaint the lines around it.
|
|
23
25
|
*/
|
|
24
26
|
const SAFE_VERSION = /^\d{1,10}\.\d{1,10}\.\d{1,10}(?:-[0-9A-Za-z.-]{1,64})?(?:\+[0-9A-Za-z.-]{1,64})?$/;
|
|
27
|
+
let standaloneBinaryPath = null;
|
|
28
|
+
/**
|
|
29
|
+
* Called by the standalone binary's entry point. That copy has no package
|
|
30
|
+
* manager behind it, so a package-manager command is the wrong upgrade advice.
|
|
31
|
+
*/
|
|
32
|
+
export function markStandaloneBinary(executablePath) {
|
|
33
|
+
standaloneBinaryPath = executablePath;
|
|
34
|
+
}
|
|
35
|
+
/** The standalone binary's own path, or null when running from a package. */
|
|
36
|
+
export function getStandaloneBinaryPath() {
|
|
37
|
+
return standaloneBinaryPath;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* `openspec install` reinstalls the standalone binary where it already is,
|
|
41
|
+
* under its own name, so the new copy cannot end up shadowed by the old one.
|
|
42
|
+
* Spelled with the full path when `openspec` on PATH is some other copy.
|
|
43
|
+
* Null when this is not the binary.
|
|
44
|
+
*/
|
|
45
|
+
function standaloneUpgradeCommand() {
|
|
46
|
+
if (!standaloneBinaryPath)
|
|
47
|
+
return null;
|
|
48
|
+
const first = findCopiesOnPath()[0];
|
|
49
|
+
if (first && samePath(first, standaloneBinaryPath))
|
|
50
|
+
return 'openspec install';
|
|
51
|
+
return process.platform === 'win32'
|
|
52
|
+
? `& '${standaloneBinaryPath.replace(/'/g, "''")}' install`
|
|
53
|
+
: `'${standaloneBinaryPath.replace(/'/g, `'\\''`)}' install`;
|
|
54
|
+
}
|
|
25
55
|
/**
|
|
26
56
|
* The check is opt-out and must never get in the way: no network in CI or
|
|
27
57
|
* tests, an explicit escape hatch for anyone offline or air-gapped, and the
|
|
@@ -304,12 +334,14 @@ export async function getAvailableCliUpdate() {
|
|
|
304
334
|
* which copy is actually answering.
|
|
305
335
|
*/
|
|
306
336
|
export function getInstallDir() {
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
}
|
|
310
|
-
catch {
|
|
337
|
+
// The binary is not a package install, so no package-manager logic applies.
|
|
338
|
+
if (standaloneBinaryPath)
|
|
311
339
|
return null;
|
|
312
|
-
|
|
340
|
+
// Resolved at runtime from this module's own (symlink-resolved) location.
|
|
341
|
+
// require.resolve would be resolved by the binary's bundler at build time,
|
|
342
|
+
// baking the build machine's path into it.
|
|
343
|
+
const packageJson = fileURLToPath(new URL('../../package.json', import.meta.url));
|
|
344
|
+
return fs.existsSync(packageJson) ? path.dirname(packageJson) : null;
|
|
313
345
|
}
|
|
314
346
|
/**
|
|
315
347
|
* True when the running CLI resolves from a `node_modules` belonging to the
|
|
@@ -508,6 +540,9 @@ function detectGlobalPackageManager(installDir) {
|
|
|
508
540
|
}
|
|
509
541
|
/** Describes the running copy without guessing when its owner is ambiguous. */
|
|
510
542
|
export function getCliInstallInfo(installDir = getInstallDir(), projectPath = '.') {
|
|
543
|
+
if (standaloneBinaryPath) {
|
|
544
|
+
return { location: path.dirname(standaloneBinaryPath), packageManager: null, scope: 'standalone' };
|
|
545
|
+
}
|
|
511
546
|
if (!installDir) {
|
|
512
547
|
return { location: null, packageManager: null, scope: null };
|
|
513
548
|
}
|
|
@@ -537,6 +572,8 @@ export function getCliInstallInfo(installDir = getInstallDir(), projectPath = '.
|
|
|
537
572
|
}
|
|
538
573
|
/** Returns a safe update command only for an install with known global ownership. */
|
|
539
574
|
export function getCliUpdateCommand(install) {
|
|
575
|
+
if (install.scope === 'standalone')
|
|
576
|
+
return standaloneUpgradeCommand();
|
|
540
577
|
if (install.scope !== 'global' || install.packageManager === null)
|
|
541
578
|
return null;
|
|
542
579
|
return GLOBAL_UPGRADE_COMMANDS[install.packageManager];
|
|
@@ -585,6 +622,12 @@ export function buildCliUpdateLines(latestVersion, installDir, projectPath, opti
|
|
|
585
622
|
*/
|
|
586
623
|
export function buildUpgradeCommandLines(installDir, projectPath) {
|
|
587
624
|
const lines = [];
|
|
625
|
+
const standalone = standaloneUpgradeCommand();
|
|
626
|
+
if (standalone) {
|
|
627
|
+
lines.push(` ${standalone}`);
|
|
628
|
+
lines.push(' Then run "openspec update" again to pick up new workflows.');
|
|
629
|
+
return lines;
|
|
630
|
+
}
|
|
588
631
|
if (isEphemeralRunnerInstall(installDir)) {
|
|
589
632
|
// That command *is* the update, so there is nothing to run afterwards.
|
|
590
633
|
lines.push(` npx ${PACKAGE_NAME}@latest update`);
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Every `openspec` on PATH, in the order a shell would find them. */
|
|
2
|
+
export declare function findCopiesOnPath(pathValue?: string, platform?: NodeJS.Platform): string[];
|
|
3
|
+
/** Whether two paths name the same file once symlinks are resolved. */
|
|
4
|
+
export declare function samePath(a: string, b: string): boolean;
|
|
5
|
+
//# sourceMappingURL=executable-path.d.ts.map
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
/** Every `openspec` on PATH, in the order a shell would find them. */
|
|
4
|
+
export function findCopiesOnPath(pathValue = process.env.PATH ?? '', platform = process.platform) {
|
|
5
|
+
const names = platform === 'win32' ? ['openspec.exe', 'openspec.cmd'] : ['openspec'];
|
|
6
|
+
const seen = new Set();
|
|
7
|
+
const copies = [];
|
|
8
|
+
for (const dir of pathValue.split(path.delimiter)) {
|
|
9
|
+
if (!dir)
|
|
10
|
+
continue;
|
|
11
|
+
for (const name of names) {
|
|
12
|
+
const candidate = path.join(dir, name);
|
|
13
|
+
try {
|
|
14
|
+
if (!fs.statSync(candidate).isFile())
|
|
15
|
+
continue;
|
|
16
|
+
if (platform !== 'win32')
|
|
17
|
+
fs.accessSync(candidate, fs.constants.X_OK);
|
|
18
|
+
const key = platform === 'win32' ? candidate.toLowerCase() : candidate;
|
|
19
|
+
if (!seen.has(key)) {
|
|
20
|
+
seen.add(key);
|
|
21
|
+
copies.push(candidate);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
// Not there, or not runnable: not a copy a shell would run.
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return copies;
|
|
30
|
+
}
|
|
31
|
+
/** Whether two paths name the same file once symlinks are resolved. */
|
|
32
|
+
export function samePath(a, b) {
|
|
33
|
+
const real = (value) => {
|
|
34
|
+
try {
|
|
35
|
+
return fs.realpathSync(value);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return path.resolve(value);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
const [x, y] = [real(a), real(b)];
|
|
42
|
+
return process.platform === 'win32' ? x.toLowerCase() === y.toLowerCase() : x === y;
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=executable-path.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fission-ai/openspec",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.15.0",
|
|
4
4
|
"description": "AI-native system for spec-driven development",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"openspec",
|
|
@@ -45,12 +45,13 @@
|
|
|
45
45
|
"@changesets/cli": "^3.0.1",
|
|
46
46
|
"@types/node": "^20.19.43",
|
|
47
47
|
"@types/picomatch": "^4.0.3",
|
|
48
|
-
"@vitest/ui": "^4.1.
|
|
48
|
+
"@vitest/ui": "^4.1.12",
|
|
49
49
|
"eslint": "^10.5.0",
|
|
50
50
|
"smol-toml": "^1.7.1",
|
|
51
51
|
"typescript": "^6.0.3",
|
|
52
52
|
"typescript-eslint": "^8.65.0",
|
|
53
|
-
"
|
|
53
|
+
"vite": "^7.3.7",
|
|
54
|
+
"vitest": "^4.1.12"
|
|
54
55
|
},
|
|
55
56
|
"dependencies": {
|
|
56
57
|
"@inquirer/core": "^12.0.0",
|