@fission-ai/openspec 1.4.1 → 1.6.0-beta.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.
- package/README.md +37 -4
- package/dist/cli/index.d.ts +5 -0
- package/dist/cli/index.js +95 -95
- package/dist/commands/change.d.ts +5 -2
- package/dist/commands/change.js +23 -55
- package/dist/commands/config.js +6 -65
- package/dist/commands/context.d.ts +3 -0
- package/dist/commands/context.js +155 -0
- package/dist/commands/doctor.d.ts +8 -0
- package/dist/commands/doctor.js +163 -0
- package/dist/commands/shared-gather.d.ts +14 -0
- package/dist/commands/shared-gather.js +31 -0
- package/dist/commands/shared-output.d.ts +18 -0
- package/dist/commands/shared-output.js +61 -0
- package/dist/commands/show.d.ts +11 -6
- package/dist/commands/show.js +70 -25
- package/dist/commands/spec.d.ts +5 -1
- package/dist/commands/spec.js +15 -4
- package/dist/commands/store.d.ts +3 -0
- package/dist/commands/store.js +547 -0
- package/dist/commands/validate.d.ts +10 -0
- package/dist/commands/validate.js +88 -40
- package/dist/commands/workflow/index.d.ts +0 -2
- package/dist/commands/workflow/index.js +0 -1
- package/dist/commands/workflow/instructions.d.ts +11 -1
- package/dist/commands/workflow/instructions.js +71 -22
- package/dist/commands/workflow/new-change.d.ts +6 -3
- package/dist/commands/workflow/new-change.js +42 -77
- package/dist/commands/workflow/shared.d.ts +15 -3
- package/dist/commands/workflow/shared.js +20 -3
- package/dist/commands/workflow/status.d.ts +2 -0
- package/dist/commands/workflow/status.js +19 -17
- package/dist/commands/workset-input.d.ts +19 -0
- package/dist/commands/workset-input.js +112 -0
- package/dist/commands/workset-prompts.d.ts +12 -0
- package/dist/commands/workset-prompts.js +143 -0
- package/dist/commands/workset.d.ts +25 -0
- package/dist/commands/workset.js +446 -0
- package/dist/core/archive.d.ts +17 -6
- package/dist/core/archive.js +220 -68
- package/dist/core/artifact-graph/index.d.ts +1 -1
- package/dist/core/artifact-graph/instruction-loader.d.ts +18 -13
- package/dist/core/artifact-graph/instruction-loader.js +8 -31
- package/dist/core/change-metadata/schema.d.ts +1 -0
- package/dist/core/change-metadata/schema.js +4 -2
- package/dist/core/change-status-policy.d.ts +5 -18
- package/dist/core/change-status-policy.js +2 -37
- package/dist/core/command-generation/adapters/bob.js +1 -14
- package/dist/core/command-generation/adapters/claude.d.ts +1 -1
- package/dist/core/command-generation/adapters/claude.js +4 -15
- package/dist/core/command-generation/adapters/cursor.js +1 -14
- package/dist/core/command-generation/adapters/index.d.ts +2 -0
- package/dist/core/command-generation/adapters/index.js +2 -0
- package/dist/core/command-generation/adapters/oh-my-pi.d.ts +21 -0
- package/dist/core/command-generation/adapters/oh-my-pi.js +44 -0
- package/dist/core/command-generation/adapters/pi.js +1 -14
- package/dist/core/command-generation/adapters/trae.d.ts +13 -0
- package/dist/core/command-generation/adapters/trae.js +48 -0
- package/dist/core/command-generation/adapters/windsurf.js +1 -14
- package/dist/core/command-generation/registry.js +4 -0
- package/dist/core/command-generation/yaml.d.ts +22 -0
- package/dist/core/command-generation/yaml.js +38 -0
- package/dist/core/completions/command-registry.js +87 -289
- package/dist/core/completions/generators/zsh-generator.js +3 -1
- package/dist/core/completions/installers/bash-installer.js +3 -0
- package/dist/core/completions/installers/fish-installer.js +8 -0
- package/dist/core/completions/installers/powershell-installer.js +30 -2
- package/dist/core/completions/installers/zsh-installer.js +3 -0
- package/dist/core/completions/shared-flags.d.ts +1 -0
- package/dist/core/completions/shared-flags.js +5 -0
- package/dist/core/config-schema.d.ts +2 -1
- package/dist/core/config-schema.js +26 -0
- package/dist/core/config.js +1 -0
- package/dist/core/file-state.d.ts +36 -0
- package/dist/core/file-state.js +112 -0
- package/dist/core/global-config.d.ts +2 -0
- package/dist/core/id.d.ts +17 -0
- package/dist/core/id.js +30 -0
- package/dist/core/index.d.ts +3 -3
- package/dist/core/index.js +3 -3
- package/dist/core/init.js +27 -6
- package/dist/core/list.d.ts +2 -0
- package/dist/core/list.js +37 -14
- package/dist/core/openers.d.ts +77 -0
- package/dist/core/openers.js +251 -0
- package/dist/core/openspec-root.d.ts +45 -0
- package/dist/core/openspec-root.js +195 -0
- package/dist/core/parsers/change-parser.js +2 -1
- package/dist/core/parsers/markdown-parser.d.ts +0 -3
- package/dist/core/parsers/markdown-parser.js +5 -59
- package/dist/core/parsers/requirement-blocks.d.ts +11 -0
- package/dist/core/parsers/requirement-blocks.js +42 -8
- package/dist/core/parsers/requirement-text.d.ts +55 -0
- package/dist/core/parsers/requirement-text.js +136 -0
- package/dist/core/planning-home.d.ts +1 -6
- package/dist/core/planning-home.js +2 -43
- package/dist/core/profile-sync-drift.js +1 -0
- package/dist/core/profiles.d.ts +2 -2
- package/dist/core/profiles.js +2 -1
- package/dist/core/project-config.d.ts +44 -1
- package/dist/core/project-config.js +160 -10
- package/dist/core/references.d.ts +63 -0
- package/dist/core/references.js +310 -0
- package/dist/core/relationship-health.d.ts +65 -0
- package/dist/core/relationship-health.js +64 -0
- package/dist/core/root-selection.d.ts +122 -0
- package/dist/core/root-selection.js +337 -0
- package/dist/core/schemas/base.schema.js +7 -2
- package/dist/core/shared/allowed-tools.d.ts +12 -0
- package/dist/core/shared/allowed-tools.js +12 -0
- package/dist/core/shared/skill-generation.js +5 -1
- package/dist/core/shared/tool-detection.d.ts +2 -2
- package/dist/core/shared/tool-detection.js +2 -0
- package/dist/core/specs-apply.d.ts +6 -1
- package/dist/core/specs-apply.js +41 -5
- package/dist/core/store/errors.d.ts +20 -0
- package/dist/core/{context-store → store}/errors.js +3 -3
- package/dist/core/store/foundation.d.ts +56 -0
- package/dist/core/store/foundation.js +251 -0
- package/dist/core/store/git.d.ts +23 -0
- package/dist/core/store/git.js +137 -0
- package/dist/core/{context-store → store}/index.d.ts +0 -1
- package/dist/core/{context-store → store}/index.js +0 -1
- package/dist/core/store/operations.d.ts +114 -0
- package/dist/core/store/operations.js +803 -0
- package/dist/core/store/registry.d.ts +58 -0
- package/dist/core/store/registry.js +275 -0
- package/dist/core/templates/skill-templates.d.ts +1 -0
- package/dist/core/templates/skill-templates.js +1 -0
- package/dist/core/templates/workflows/apply-change.js +5 -4
- package/dist/core/templates/workflows/archive-change.js +5 -4
- package/dist/core/templates/workflows/bulk-archive-change.js +5 -4
- package/dist/core/templates/workflows/continue-change.js +7 -2
- package/dist/core/templates/workflows/explore.js +5 -0
- package/dist/core/templates/workflows/ff-change.js +5 -0
- package/dist/core/templates/workflows/new-change.js +5 -0
- package/dist/core/templates/workflows/onboard.js +4 -1
- package/dist/core/templates/workflows/propose.js +5 -0
- package/dist/core/templates/workflows/store-selection.d.ts +8 -0
- package/dist/core/templates/workflows/store-selection.js +8 -0
- package/dist/core/templates/workflows/sync-specs.js +5 -4
- package/dist/core/templates/workflows/update-change.d.ts +10 -0
- package/dist/core/templates/workflows/update-change.js +167 -0
- package/dist/core/templates/workflows/verify-change.js +5 -4
- package/dist/core/update.js +4 -4
- package/dist/core/validation/validator.d.ts +7 -0
- package/dist/core/validation/validator.js +101 -38
- package/dist/core/view.js +1 -1
- package/dist/core/working-set.d.ts +47 -0
- package/dist/core/working-set.js +43 -0
- package/dist/core/worksets.d.ts +75 -0
- package/dist/core/worksets.js +245 -0
- package/dist/core/zod-issues.d.ts +4 -0
- package/dist/core/zod-issues.js +10 -0
- package/dist/utils/change-metadata.d.ts +1 -0
- package/dist/utils/change-metadata.js +1 -1
- package/dist/utils/change-utils.js +15 -0
- package/dist/utils/file-system.js +36 -17
- package/dist/utils/task-progress.d.ts +10 -1
- package/dist/utils/task-progress.js +66 -2
- package/package.json +26 -23
- package/dist/commands/context-store.d.ts +0 -3
- package/dist/commands/context-store.js +0 -475
- package/dist/commands/initiative.d.ts +0 -13
- package/dist/commands/initiative.js +0 -318
- package/dist/commands/workflow/initiative-link.d.ts +0 -24
- package/dist/commands/workflow/initiative-link.js +0 -47
- package/dist/commands/workflow/set-change.d.ts +0 -13
- package/dist/commands/workflow/set-change.js +0 -87
- package/dist/commands/workspace/context-status.d.ts +0 -4
- package/dist/commands/workspace/context-status.js +0 -59
- package/dist/commands/workspace/open-target-selection.d.ts +0 -13
- package/dist/commands/workspace/open-target-selection.js +0 -146
- package/dist/commands/workspace/open-view.d.ts +0 -62
- package/dist/commands/workspace/open-view.js +0 -249
- package/dist/commands/workspace/open.d.ts +0 -37
- package/dist/commands/workspace/open.js +0 -110
- package/dist/commands/workspace/opener-selection.d.ts +0 -11
- package/dist/commands/workspace/opener-selection.js +0 -98
- package/dist/commands/workspace/operations.d.ts +0 -29
- package/dist/commands/workspace/operations.js +0 -543
- package/dist/commands/workspace/prompt-theme.d.ts +0 -29
- package/dist/commands/workspace/prompt-theme.js +0 -24
- package/dist/commands/workspace/registration.d.ts +0 -13
- package/dist/commands/workspace/registration.js +0 -84
- package/dist/commands/workspace/selection.d.ts +0 -8
- package/dist/commands/workspace/selection.js +0 -104
- package/dist/commands/workspace/setup-prompts.d.ts +0 -13
- package/dist/commands/workspace/setup-prompts.js +0 -121
- package/dist/commands/workspace/types.d.ts +0 -103
- package/dist/commands/workspace/types.js +0 -36
- package/dist/commands/workspace.d.ts +0 -5
- package/dist/commands/workspace.js +0 -574
- package/dist/core/collections/index.d.ts +0 -3
- package/dist/core/collections/index.js +0 -3
- package/dist/core/collections/initiatives/collection.d.ts +0 -4
- package/dist/core/collections/initiatives/collection.js +0 -17
- package/dist/core/collections/initiatives/index.d.ts +0 -6
- package/dist/core/collections/initiatives/index.js +0 -6
- package/dist/core/collections/initiatives/operations.d.ts +0 -49
- package/dist/core/collections/initiatives/operations.js +0 -175
- package/dist/core/collections/initiatives/resolution.d.ts +0 -87
- package/dist/core/collections/initiatives/resolution.js +0 -374
- package/dist/core/collections/initiatives/schema.d.ts +0 -41
- package/dist/core/collections/initiatives/schema.js +0 -134
- package/dist/core/collections/initiatives/templates.d.ts +0 -12
- package/dist/core/collections/initiatives/templates.js +0 -90
- package/dist/core/collections/runtime.d.ts +0 -46
- package/dist/core/collections/runtime.js +0 -194
- package/dist/core/context-store/binding.d.ts +0 -53
- package/dist/core/context-store/binding.js +0 -197
- package/dist/core/context-store/errors.d.ts +0 -20
- package/dist/core/context-store/foundation.d.ts +0 -55
- package/dist/core/context-store/foundation.js +0 -321
- package/dist/core/context-store/operations.d.ts +0 -85
- package/dist/core/context-store/operations.js +0 -528
- package/dist/core/context-store/registry.d.ts +0 -45
- package/dist/core/context-store/registry.js +0 -229
- package/dist/core/workspace/foundation.d.ts +0 -67
- package/dist/core/workspace/foundation.js +0 -295
- package/dist/core/workspace/index.d.ts +0 -8
- package/dist/core/workspace/index.js +0 -8
- package/dist/core/workspace/legacy-state.d.ts +0 -28
- package/dist/core/workspace/legacy-state.js +0 -200
- package/dist/core/workspace/link-input.d.ts +0 -9
- package/dist/core/workspace/link-input.js +0 -32
- package/dist/core/workspace/open-surface.d.ts +0 -45
- package/dist/core/workspace/open-surface.js +0 -215
- package/dist/core/workspace/openers.d.ts +0 -21
- package/dist/core/workspace/openers.js +0 -124
- package/dist/core/workspace/registry.d.ts +0 -24
- package/dist/core/workspace/registry.js +0 -146
- package/dist/core/workspace/skills.d.ts +0 -57
- package/dist/core/workspace/skills.js +0 -334
- package/dist/core/workspace/state-io.d.ts +0 -10
- package/dist/core/workspace/state-io.js +0 -121
- package/schemas/workspace-planning/schema.yaml +0 -72
- package/schemas/workspace-planning/templates/design.md +0 -33
- package/schemas/workspace-planning/templates/proposal.md +0 -28
- package/schemas/workspace-planning/templates/spec.md +0 -9
- package/schemas/workspace-planning/templates/tasks.md +0 -15
|
@@ -24,5 +24,10 @@ export const COMMON_FLAGS = {
|
|
|
24
24
|
takesValue: true,
|
|
25
25
|
values: ['change', 'spec'],
|
|
26
26
|
},
|
|
27
|
+
store: {
|
|
28
|
+
name: 'store',
|
|
29
|
+
description: "Store id to use as the OpenSpec root (a store is a standalone OpenSpec repo you've registered)",
|
|
30
|
+
takesValue: true,
|
|
31
|
+
},
|
|
27
32
|
};
|
|
28
33
|
//# sourceMappingURL=shared-flags.js.map
|
|
@@ -58,13 +58,14 @@ export declare function deleteNestedValue(obj: Record<string, unknown>, path: st
|
|
|
58
58
|
* Coerce a string value to its appropriate type.
|
|
59
59
|
* - "true" / "false" -> boolean
|
|
60
60
|
* - Numeric strings -> number
|
|
61
|
+
* - JSON arrays/objects -> parsed containers
|
|
61
62
|
* - Everything else -> string
|
|
62
63
|
*
|
|
63
64
|
* @param value - The string value to coerce
|
|
64
65
|
* @param forceString - If true, always return the value as a string
|
|
65
66
|
* @returns The coerced value
|
|
66
67
|
*/
|
|
67
|
-
export declare function coerceValue(value: string, forceString?: boolean): string | number | boolean
|
|
68
|
+
export declare function coerceValue(value: string, forceString?: boolean): string | number | boolean | unknown[] | Record<string, unknown>;
|
|
68
69
|
/**
|
|
69
70
|
* Format a value for YAML-like display.
|
|
70
71
|
*
|
|
@@ -125,6 +125,7 @@ export function deleteNestedValue(obj, path) {
|
|
|
125
125
|
* Coerce a string value to its appropriate type.
|
|
126
126
|
* - "true" / "false" -> boolean
|
|
127
127
|
* - Numeric strings -> number
|
|
128
|
+
* - JSON arrays/objects -> parsed containers
|
|
128
129
|
* - Everything else -> string
|
|
129
130
|
*
|
|
130
131
|
* @param value - The string value to coerce
|
|
@@ -147,8 +148,33 @@ export function coerceValue(value, forceString = false) {
|
|
|
147
148
|
if (!isNaN(num) && isFinite(num) && value.trim() !== '') {
|
|
148
149
|
return num;
|
|
149
150
|
}
|
|
151
|
+
const jsonContainer = parseJsonContainer(value);
|
|
152
|
+
if (jsonContainer !== undefined) {
|
|
153
|
+
return jsonContainer;
|
|
154
|
+
}
|
|
150
155
|
return value;
|
|
151
156
|
}
|
|
157
|
+
function parseJsonContainer(value) {
|
|
158
|
+
const trimmed = value.trim();
|
|
159
|
+
const looksLikeContainer = (trimmed.startsWith('[') && trimmed.endsWith(']')) ||
|
|
160
|
+
(trimmed.startsWith('{') && trimmed.endsWith('}'));
|
|
161
|
+
if (!looksLikeContainer) {
|
|
162
|
+
return undefined;
|
|
163
|
+
}
|
|
164
|
+
try {
|
|
165
|
+
const parsed = JSON.parse(trimmed);
|
|
166
|
+
if (Array.isArray(parsed)) {
|
|
167
|
+
return parsed;
|
|
168
|
+
}
|
|
169
|
+
if (parsed !== null && typeof parsed === 'object') {
|
|
170
|
+
return parsed;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
return undefined;
|
|
175
|
+
}
|
|
176
|
+
return undefined;
|
|
177
|
+
}
|
|
152
178
|
/**
|
|
153
179
|
* Format a value for YAML-like display.
|
|
154
180
|
*
|
package/dist/core/config.js
CHANGED
|
@@ -27,6 +27,7 @@ export const AI_TOOLS = [
|
|
|
27
27
|
{ name: 'Kiro', value: 'kiro', available: true, successLabel: 'Kiro', skillsDir: '.kiro' },
|
|
28
28
|
{ name: 'Lingma', value: 'lingma', available: true, successLabel: 'Lingma', skillsDir: '.lingma' },
|
|
29
29
|
{ name: 'Mistral Vibe', value: 'vibe', available: true, successLabel: 'Mistral Vibe', skillsDir: '.vibe' },
|
|
30
|
+
{ name: 'Oh My Pi', value: 'oh-my-pi', available: true, successLabel: 'Oh My Pi', skillsDir: '.omp' },
|
|
30
31
|
{ name: 'OpenCode', value: 'opencode', available: true, successLabel: 'OpenCode', skillsDir: '.opencode' },
|
|
31
32
|
{ name: 'Pi', value: 'pi', available: true, successLabel: 'Pi', skillsDir: '.pi' },
|
|
32
33
|
{ name: 'Qoder', value: 'qoder', available: true, successLabel: 'Qoder', skillsDir: '.qoder' },
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import * as nodeFs from 'node:fs';
|
|
2
|
+
import { StoreError } from './store/errors.js';
|
|
3
|
+
/**
|
|
4
|
+
* Shared machine-local state-file mechanics (extracted from the store
|
|
5
|
+
* registry in slice 7.1, its second consumer). Callers own the
|
|
6
|
+
* diagnostic data (code, target, wording); the factory owns the
|
|
7
|
+
* shared mechanics - the fix strings describe the lock's own
|
|
8
|
+
* behavior (stale-steal, creation), so their templates live here.
|
|
9
|
+
*/
|
|
10
|
+
export type FileLockErrorKind = 'create-failed' | 'timeout';
|
|
11
|
+
export interface FileLockErrorInfo {
|
|
12
|
+
lockPath: string;
|
|
13
|
+
/** The original errno error for 'create-failed'. */
|
|
14
|
+
cause?: unknown;
|
|
15
|
+
}
|
|
16
|
+
export interface FileLockOptions {
|
|
17
|
+
lockPath: string;
|
|
18
|
+
errorFor: (kind: FileLockErrorKind, info: FileLockErrorInfo) => Error;
|
|
19
|
+
}
|
|
20
|
+
export interface LockErrorData {
|
|
21
|
+
/** Noun phrase for the create-failed message, e.g. "the registry lock file". */
|
|
22
|
+
createSubject: string;
|
|
23
|
+
/** The full timeout message, e.g. "Store registry is busy." */
|
|
24
|
+
busyMessage: string;
|
|
25
|
+
code: string;
|
|
26
|
+
target: string;
|
|
27
|
+
}
|
|
28
|
+
/** One template for lock diagnostics; callers supply the data. */
|
|
29
|
+
export declare function makeLockErrorFactory(data: LockErrorData): (kind: FileLockErrorKind, info: FileLockErrorInfo) => StoreError;
|
|
30
|
+
export declare function isNodeErrorCode(error: unknown, code: string): boolean;
|
|
31
|
+
export declare function pathIsFile(filePath: string): Promise<boolean>;
|
|
32
|
+
export declare function pathIsDirectory(dirPath: string): Promise<boolean>;
|
|
33
|
+
export declare function writeFileAtomically(filePath: string, content: string): Promise<void>;
|
|
34
|
+
export declare function acquireFileLock(options: FileLockOptions): Promise<nodeFs.promises.FileHandle>;
|
|
35
|
+
export declare function releaseFileLock(lock: nodeFs.promises.FileHandle, lockPath: string): Promise<void>;
|
|
36
|
+
//# sourceMappingURL=file-state.d.ts.map
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import * as nodeFs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { FileSystemUtils } from '../utils/file-system.js';
|
|
4
|
+
import { StoreError } from './store/errors.js';
|
|
5
|
+
const fs = nodeFs.promises;
|
|
6
|
+
/** One template for lock diagnostics; callers supply the data. */
|
|
7
|
+
export function makeLockErrorFactory(data) {
|
|
8
|
+
return (kind, info) => {
|
|
9
|
+
if (kind === 'create-failed') {
|
|
10
|
+
// A permission or filesystem problem, not contention - say so.
|
|
11
|
+
return new StoreError(`Cannot create ${data.createSubject} ${info.lockPath} (${info.cause?.code ?? info.cause}).`, data.code, {
|
|
12
|
+
target: data.target,
|
|
13
|
+
fix: `Check permissions on ${path.dirname(info.lockPath)}.`,
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
return new StoreError(data.busyMessage, data.code, {
|
|
17
|
+
target: data.target,
|
|
18
|
+
fix: `Retry shortly; if this persists, delete the stale lock file ${info.lockPath}.`,
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
const STALE_LOCK_THRESHOLD_MS = 30_000;
|
|
23
|
+
const LOCK_DEADLINE_MS = 5000;
|
|
24
|
+
const LOCK_POLL_MS = 25;
|
|
25
|
+
export function isNodeErrorCode(error, code) {
|
|
26
|
+
return (typeof error === 'object' &&
|
|
27
|
+
error !== null &&
|
|
28
|
+
'code' in error &&
|
|
29
|
+
error.code === code);
|
|
30
|
+
}
|
|
31
|
+
export async function pathIsFile(filePath) {
|
|
32
|
+
try {
|
|
33
|
+
return (await fs.stat(filePath)).isFile();
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
// Deliberately not FileSystemUtils.directoryExists: that variant
|
|
40
|
+
// debug-logs non-ENOENT failures, which is noise inside prompt
|
|
41
|
+
// validators, and pathIsFile has no FileSystemUtils equivalent - the
|
|
42
|
+
// silent symmetric pair lives here.
|
|
43
|
+
export async function pathIsDirectory(dirPath) {
|
|
44
|
+
try {
|
|
45
|
+
return (await fs.stat(dirPath)).isDirectory();
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
async function sleep(milliseconds) {
|
|
52
|
+
await new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
53
|
+
}
|
|
54
|
+
export async function writeFileAtomically(filePath, content) {
|
|
55
|
+
const dirPath = path.dirname(filePath);
|
|
56
|
+
await FileSystemUtils.createDirectory(dirPath);
|
|
57
|
+
const tempPath = path.join(dirPath, `.${path.basename(filePath)}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`);
|
|
58
|
+
try {
|
|
59
|
+
await fs.writeFile(tempPath, content, 'utf-8');
|
|
60
|
+
await fs.rename(tempPath, filePath);
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
await fs.rm(tempPath, { force: true }).catch(() => undefined);
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
export async function acquireFileLock(options) {
|
|
68
|
+
const { lockPath, errorFor } = options;
|
|
69
|
+
const lockDir = path.dirname(lockPath);
|
|
70
|
+
await FileSystemUtils.createDirectory(lockDir);
|
|
71
|
+
if (!(await FileSystemUtils.canWriteFile(lockDir))) {
|
|
72
|
+
throw errorFor('create-failed', { lockPath, cause: 'EACCES' });
|
|
73
|
+
}
|
|
74
|
+
const deadline = Date.now() + LOCK_DEADLINE_MS;
|
|
75
|
+
while (true) {
|
|
76
|
+
try {
|
|
77
|
+
return await fs.open(lockPath, 'wx');
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
if (!isNodeErrorCode(error, 'EEXIST')) {
|
|
81
|
+
// A permission or filesystem problem, not contention - say so.
|
|
82
|
+
throw errorFor('create-failed', { lockPath, cause: error });
|
|
83
|
+
}
|
|
84
|
+
// A crashed process leaves the lock behind forever; state-file
|
|
85
|
+
// writes are sub-second, so an old lock is an orphan - steal it.
|
|
86
|
+
let staleStolen = false;
|
|
87
|
+
try {
|
|
88
|
+
const lockStat = await fs.stat(lockPath);
|
|
89
|
+
if (Date.now() - lockStat.mtimeMs > STALE_LOCK_THRESHOLD_MS) {
|
|
90
|
+
await fs.rm(lockPath, { force: true });
|
|
91
|
+
staleStolen = true;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
// The holder released between open and stat - retry, but stay
|
|
96
|
+
// bounded: a persistently failing stat (EPERM, delete-pending)
|
|
97
|
+
// must hit the deadline instead of spinning forever.
|
|
98
|
+
}
|
|
99
|
+
if (!staleStolen) {
|
|
100
|
+
if (Date.now() >= deadline) {
|
|
101
|
+
throw errorFor('timeout', { lockPath });
|
|
102
|
+
}
|
|
103
|
+
await sleep(LOCK_POLL_MS);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
export async function releaseFileLock(lock, lockPath) {
|
|
109
|
+
await lock.close().catch(() => undefined);
|
|
110
|
+
await fs.rm(lockPath, { force: true }).catch(() => undefined);
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=file-state.js.map
|
|
@@ -8,6 +8,8 @@ export interface GlobalConfig {
|
|
|
8
8
|
profile?: Profile;
|
|
9
9
|
delivery?: Delivery;
|
|
10
10
|
workflows?: string[];
|
|
11
|
+
/** Workset opener rows (slice 7.1); hand-edited, validated on use. */
|
|
12
|
+
openers?: unknown;
|
|
11
13
|
}
|
|
12
14
|
/**
|
|
13
15
|
* Gets the global configuration directory path following XDG Base Directory Specification.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one kebab id grammar. Store ids, change ids, and legacy initiative ids
|
|
3
|
+
* all share it.
|
|
4
|
+
*/
|
|
5
|
+
export declare const KEBAB_ID_REGEX: RegExp;
|
|
6
|
+
export declare function isKebabId(value: string): boolean;
|
|
7
|
+
/** Human rendering of the grammar, shared so the wording never forks. */
|
|
8
|
+
export declare const KEBAB_ID_DESCRIPTION = "must be kebab-case with lowercase letters, numbers, and single hyphen separators";
|
|
9
|
+
/** The fix-line twin of KEBAB_ID_DESCRIPTION, shared for the same reason. */
|
|
10
|
+
export declare const KEBAB_ID_FIX = "Use kebab-case with lowercase letters, numbers, and single hyphen separators.";
|
|
11
|
+
/**
|
|
12
|
+
* The folder-safe-name grammar (store ids layer the kebab grammar on
|
|
13
|
+
* top of it; workset member labels use it alone). Returns a problem
|
|
14
|
+
* description, or null when valid.
|
|
15
|
+
*/
|
|
16
|
+
export declare function folderStyleNameProblem(value: string, label: string): string | null;
|
|
17
|
+
//# sourceMappingURL=id.d.ts.map
|
package/dist/core/id.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one kebab id grammar. Store ids, change ids, and legacy initiative ids
|
|
3
|
+
* all share it.
|
|
4
|
+
*/
|
|
5
|
+
export const KEBAB_ID_REGEX = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
|
|
6
|
+
export function isKebabId(value) {
|
|
7
|
+
return KEBAB_ID_REGEX.test(value);
|
|
8
|
+
}
|
|
9
|
+
/** Human rendering of the grammar, shared so the wording never forks. */
|
|
10
|
+
export const KEBAB_ID_DESCRIPTION = 'must be kebab-case with lowercase letters, numbers, and single hyphen separators';
|
|
11
|
+
/** The fix-line twin of KEBAB_ID_DESCRIPTION, shared for the same reason. */
|
|
12
|
+
export const KEBAB_ID_FIX = 'Use kebab-case with lowercase letters, numbers, and single hyphen separators.';
|
|
13
|
+
/**
|
|
14
|
+
* The folder-safe-name grammar (store ids layer the kebab grammar on
|
|
15
|
+
* top of it; workset member labels use it alone). Returns a problem
|
|
16
|
+
* description, or null when valid.
|
|
17
|
+
*/
|
|
18
|
+
export function folderStyleNameProblem(value, label) {
|
|
19
|
+
if (value.length === 0) {
|
|
20
|
+
return `${label} must not be empty`;
|
|
21
|
+
}
|
|
22
|
+
if (value === '.' || value === '..') {
|
|
23
|
+
return `${label} must not be '${value}'`;
|
|
24
|
+
}
|
|
25
|
+
if (/[\\/]/u.test(value)) {
|
|
26
|
+
return `${label} must not contain path separators`;
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=id.js.map
|
package/dist/core/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { GLOBAL_CONFIG_DIR_NAME, GLOBAL_CONFIG_FILE_NAME, GLOBAL_DATA_DIR_NAME, type GlobalDataDirOptions, type GlobalConfig, getGlobalConfigDir, getGlobalConfigPath, getGlobalConfig, saveGlobalConfig, getGlobalDataDir } from './global-config.js';
|
|
2
|
-
export * from './
|
|
3
|
-
export * from './
|
|
4
|
-
export * from './collections/index.js';
|
|
2
|
+
export * from './references.js';
|
|
3
|
+
export * from './store/index.js';
|
|
5
4
|
export * from './planning-home.js';
|
|
5
|
+
export * from './openspec-root.js';
|
|
6
6
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/core/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Core OpenSpec logic will be implemented here
|
|
2
2
|
export { GLOBAL_CONFIG_DIR_NAME, GLOBAL_CONFIG_FILE_NAME, GLOBAL_DATA_DIR_NAME, getGlobalConfigDir, getGlobalConfigPath, getGlobalConfig, saveGlobalConfig, getGlobalDataDir } from './global-config.js';
|
|
3
|
-
export * from './
|
|
4
|
-
export * from './
|
|
5
|
-
export * from './collections/index.js';
|
|
3
|
+
export * from './references.js';
|
|
4
|
+
export * from './store/index.js';
|
|
6
5
|
export * from './planning-home.js';
|
|
6
|
+
export * from './openspec-root.js';
|
|
7
7
|
//# sourceMappingURL=index.js.map
|
package/dist/core/init.js
CHANGED
|
@@ -10,6 +10,8 @@ import ora from 'ora';
|
|
|
10
10
|
import * as fs from 'fs';
|
|
11
11
|
import { createRequire } from 'module';
|
|
12
12
|
import { FileSystemUtils } from '../utils/file-system.js';
|
|
13
|
+
import { classifyOpenSpecDir, storePointerProblem } from './project-config.js';
|
|
14
|
+
import { findRepoPlanningRootSync } from './planning-home.js';
|
|
13
15
|
import { transformToHyphenCommands } from '../utils/command-references.js';
|
|
14
16
|
import { AI_TOOLS, OPENSPEC_DIR_NAME, } from './config.js';
|
|
15
17
|
import { PALETTE } from './styles/palette.js';
|
|
@@ -37,6 +39,7 @@ const WORKFLOW_TO_SKILL_DIR = {
|
|
|
37
39
|
'new': 'openspec-new-change',
|
|
38
40
|
'continue': 'openspec-continue-change',
|
|
39
41
|
'apply': 'openspec-apply-change',
|
|
42
|
+
'update': 'openspec-update-change',
|
|
40
43
|
'ff': 'openspec-ff-change',
|
|
41
44
|
'sync': 'openspec-sync-specs',
|
|
42
45
|
'archive': 'openspec-archive-change',
|
|
@@ -65,6 +68,28 @@ export class InitCommand {
|
|
|
65
68
|
const openspecPath = path.join(projectPath, openspecDir);
|
|
66
69
|
// Validation happens silently in the background
|
|
67
70
|
const extendMode = await this.validate(projectPath, openspecPath);
|
|
71
|
+
// Pointer guard (slice 3.2): a config-only openspec/ with a store:
|
|
72
|
+
// declaration is externalized planning, not a root to extend — and a
|
|
73
|
+
// subdirectory of such a repo must not silently grow a nested root.
|
|
74
|
+
// Refuse before legacy cleanup, migration, or prompts touch anything.
|
|
75
|
+
// In extend mode the walk finds projectPath itself; otherwise it
|
|
76
|
+
// finds the nearest ancestor root (so pointer-repo subdirectories
|
|
77
|
+
// refuse exactly where a normal command would resolve the pointer).
|
|
78
|
+
const guardRoot = findRepoPlanningRootSync(projectPath);
|
|
79
|
+
if (guardRoot) {
|
|
80
|
+
const { hasPlanningShape, pointer } = classifyOpenSpecDir(guardRoot);
|
|
81
|
+
if (!hasPlanningShape) {
|
|
82
|
+
if (pointer.malformed) {
|
|
83
|
+
throw new Error(`The store declaration in ${pointer.filePath} is invalid (` +
|
|
84
|
+
storePointerProblem(pointer.malformed) +
|
|
85
|
+
`). Fix or remove the store: line before running openspec init.`);
|
|
86
|
+
}
|
|
87
|
+
if (pointer.value !== undefined) {
|
|
88
|
+
throw new Error(`This repo's planning is externalized to store '${pointer.value}' (${pointer.filePath}). ` +
|
|
89
|
+
`Remove the store: line first to convert this repo to a local OpenSpec root.`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
68
93
|
// Check for legacy artifacts and handle cleanup
|
|
69
94
|
await this.handleLegacyCleanup(projectPath, extendMode);
|
|
70
95
|
// Detect available tools in the project (task 7.1)
|
|
@@ -379,8 +404,8 @@ export class InitCommand {
|
|
|
379
404
|
const skillDir = path.join(skillsDir, dirName);
|
|
380
405
|
const skillFile = path.join(skillDir, 'SKILL.md');
|
|
381
406
|
// Generate SKILL.md content with YAML frontmatter including generatedBy
|
|
382
|
-
// Use hyphen-based command references for tools where filename
|
|
383
|
-
const transformer = (tool.value === 'opencode' || tool.value === 'pi') ? transformToHyphenCommands : undefined;
|
|
407
|
+
// Use hyphen-based command references for tools where filename === command name (oh-my-pi, opencode, pi)
|
|
408
|
+
const transformer = (tool.value === 'opencode' || tool.value === 'pi' || tool.value === 'oh-my-pi') ? transformToHyphenCommands : undefined;
|
|
384
409
|
const skillContent = generateSkillContent(template, OPENSPEC_VERSION, transformer);
|
|
385
410
|
// Write the skill file
|
|
386
411
|
await FileSystemUtils.writeFile(skillFile, skillContent);
|
|
@@ -440,10 +465,6 @@ export class InitCommand {
|
|
|
440
465
|
if (configYamlExists || configYmlExists) {
|
|
441
466
|
return 'exists';
|
|
442
467
|
}
|
|
443
|
-
// In non-interactive mode without --force, skip config creation
|
|
444
|
-
if (!this.canPromptInteractively() && !this.force) {
|
|
445
|
-
return 'skipped';
|
|
446
|
-
}
|
|
447
468
|
try {
|
|
448
469
|
const yamlContent = serializeConfig({ schema: DEFAULT_SCHEMA });
|
|
449
470
|
await FileSystemUtils.writeFile(configPath, yamlContent);
|
package/dist/core/list.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import type { RootOutput } from './root-selection.js';
|
|
1
2
|
interface ListOptions {
|
|
2
3
|
sort?: 'recent' | 'name';
|
|
3
4
|
json?: boolean;
|
|
5
|
+
root?: RootOutput;
|
|
4
6
|
}
|
|
5
7
|
export declare class ListCommand {
|
|
6
8
|
execute(targetPath?: string, mode?: 'changes' | 'specs', options?: ListOptions): Promise<void>;
|
package/dist/core/list.js
CHANGED
|
@@ -4,6 +4,22 @@ import { getTaskProgressForChange, formatTaskStatus } from '../utils/task-progre
|
|
|
4
4
|
import { readFileSync } from 'fs';
|
|
5
5
|
import { join } from 'path';
|
|
6
6
|
import { MarkdownParser } from './parsers/markdown-parser.js';
|
|
7
|
+
function isMissingPathError(error) {
|
|
8
|
+
return (typeof error === 'object' &&
|
|
9
|
+
error !== null &&
|
|
10
|
+
'code' in error &&
|
|
11
|
+
error.code === 'ENOENT');
|
|
12
|
+
}
|
|
13
|
+
async function readChangeDirectoryEntries(changesDir) {
|
|
14
|
+
try {
|
|
15
|
+
return await fs.readdir(changesDir, { withFileTypes: true });
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
if (isMissingPathError(error))
|
|
19
|
+
return [];
|
|
20
|
+
throw error;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
7
23
|
/**
|
|
8
24
|
* Get the most recent modification time of any file in a directory (recursive).
|
|
9
25
|
* Falls back to the directory's own mtime if no files are found.
|
|
@@ -61,24 +77,17 @@ function formatRelativeTime(date) {
|
|
|
61
77
|
}
|
|
62
78
|
export class ListCommand {
|
|
63
79
|
async execute(targetPath = '.', mode = 'changes', options = {}) {
|
|
64
|
-
const { sort = 'recent', json = false } = options;
|
|
80
|
+
const { sort = 'recent', json = false, root } = options;
|
|
65
81
|
if (mode === 'changes') {
|
|
66
82
|
const changesDir = path.join(targetPath, 'openspec', 'changes');
|
|
67
|
-
// Check if changes directory exists
|
|
68
|
-
try {
|
|
69
|
-
await fs.access(changesDir);
|
|
70
|
-
}
|
|
71
|
-
catch {
|
|
72
|
-
throw new Error("No OpenSpec changes directory found. Run 'openspec init' first.");
|
|
73
|
-
}
|
|
74
83
|
// Get all directories in changes (excluding archive)
|
|
75
|
-
const entries = await
|
|
84
|
+
const entries = await readChangeDirectoryEntries(changesDir);
|
|
76
85
|
const changeDirs = entries
|
|
77
86
|
.filter(entry => entry.isDirectory() && entry.name !== 'archive')
|
|
78
87
|
.map(entry => entry.name);
|
|
79
88
|
if (changeDirs.length === 0) {
|
|
80
89
|
if (json) {
|
|
81
|
-
console.log(JSON.stringify({ changes: [] }));
|
|
90
|
+
console.log(JSON.stringify({ changes: [], ...(root ? { root } : {}) }, null, 2));
|
|
82
91
|
}
|
|
83
92
|
else {
|
|
84
93
|
console.log('No active changes found.');
|
|
@@ -88,7 +97,7 @@ export class ListCommand {
|
|
|
88
97
|
// Collect information about each change
|
|
89
98
|
const changes = [];
|
|
90
99
|
for (const changeDir of changeDirs) {
|
|
91
|
-
const progress = await getTaskProgressForChange(changesDir, changeDir);
|
|
100
|
+
const progress = await getTaskProgressForChange(changesDir, changeDir, targetPath);
|
|
92
101
|
const changePath = path.join(changesDir, changeDir);
|
|
93
102
|
const lastModified = await getLastModified(changePath);
|
|
94
103
|
changes.push({
|
|
@@ -114,7 +123,7 @@ export class ListCommand {
|
|
|
114
123
|
lastModified: c.lastModified.toISOString(),
|
|
115
124
|
status: c.totalTasks === 0 ? 'no-tasks' : c.completedTasks === c.totalTasks ? 'complete' : 'in-progress'
|
|
116
125
|
}));
|
|
117
|
-
console.log(JSON.stringify({ changes: jsonOutput }, null, 2));
|
|
126
|
+
console.log(JSON.stringify({ changes: jsonOutput, ...(root ? { root } : {}) }, null, 2));
|
|
118
127
|
return;
|
|
119
128
|
}
|
|
120
129
|
// Display results
|
|
@@ -135,13 +144,23 @@ export class ListCommand {
|
|
|
135
144
|
await fs.access(specsDir);
|
|
136
145
|
}
|
|
137
146
|
catch {
|
|
138
|
-
|
|
147
|
+
if (json) {
|
|
148
|
+
console.log(JSON.stringify({ specs: [], ...(root ? { root } : {}) }, null, 2));
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
console.log('No specs found.');
|
|
152
|
+
}
|
|
139
153
|
return;
|
|
140
154
|
}
|
|
141
155
|
const entries = await fs.readdir(specsDir, { withFileTypes: true });
|
|
142
156
|
const specDirs = entries.filter(e => e.isDirectory()).map(e => e.name);
|
|
143
157
|
if (specDirs.length === 0) {
|
|
144
|
-
|
|
158
|
+
if (json) {
|
|
159
|
+
console.log(JSON.stringify({ specs: [], ...(root ? { root } : {}) }, null, 2));
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
console.log('No specs found.');
|
|
163
|
+
}
|
|
145
164
|
return;
|
|
146
165
|
}
|
|
147
166
|
const specs = [];
|
|
@@ -159,6 +178,10 @@ export class ListCommand {
|
|
|
159
178
|
}
|
|
160
179
|
}
|
|
161
180
|
specs.sort((a, b) => a.id.localeCompare(b.id));
|
|
181
|
+
if (json) {
|
|
182
|
+
console.log(JSON.stringify({ specs, ...(root ? { root } : {}) }, null, 2));
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
162
185
|
console.log('Specs:');
|
|
163
186
|
const padding = ' ';
|
|
164
187
|
const nameWidth = Math.max(...specs.map(s => s.id.length));
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { WorksetMember } from './worksets.js';
|
|
2
|
+
/**
|
|
3
|
+
* The workset opener table (slice 7.1). Supporting a new tool is
|
|
4
|
+
* configuration, not code: every tool is an instance of one of exactly
|
|
5
|
+
* two launch styles - 'workspace-file' (invoke with the generated
|
|
6
|
+
* .code-workspace) or 'attach-dirs' (pre-args plus one attach flag per
|
|
7
|
+
* member; no positional, ever - agent sessions open clean). Users add
|
|
8
|
+
* tools or adjust parameters under the global config file's `openers`
|
|
9
|
+
* key (the git difftool/mergetool pattern).
|
|
10
|
+
*/
|
|
11
|
+
export type OpenerStyle = 'workspace-file' | 'attach-dirs';
|
|
12
|
+
export interface OpenerDefinition {
|
|
13
|
+
id: string;
|
|
14
|
+
label: string;
|
|
15
|
+
style: OpenerStyle;
|
|
16
|
+
command: string;
|
|
17
|
+
/** Pre-args before any attach flags or the workspace-file path. */
|
|
18
|
+
args: string[];
|
|
19
|
+
/** attach-dirs only; one flag + path pair per member. */
|
|
20
|
+
attachFlag: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Temporary kill-switch (2026-06): worksets open only in IDE-style
|
|
24
|
+
* ('workspace-file') tools while the CLI-agent ('attach-dirs') open flow
|
|
25
|
+
* is reworked. The agents (Claude Code, codex) launch in a single primary
|
|
26
|
+
* cwd rather than a true combined multi-root view, which makes "where does
|
|
27
|
+
* my change land?" ambiguous. Default off; set
|
|
28
|
+
* OPENSPEC_ENABLE_CLI_AGENT_OPENERS=1 to restore them (internal rollback seam).
|
|
29
|
+
*/
|
|
30
|
+
export declare function isCliAgentOpenersEnabled(): boolean;
|
|
31
|
+
/** Whether a tool can be opened right now (CLI-agent styles are gated). */
|
|
32
|
+
export declare function isOpenerEnabled(opener: OpenerDefinition): boolean;
|
|
33
|
+
export declare const BUILTIN_OPENERS: readonly OpenerDefinition[];
|
|
34
|
+
export declare function mergeOpenerTable(rawOpeners: unknown, configPath: string): OpenerDefinition[];
|
|
35
|
+
export interface OpenerScanOptions {
|
|
36
|
+
env?: NodeJS.ProcessEnv;
|
|
37
|
+
platform?: NodeJS.Platform;
|
|
38
|
+
/** Stat seam for tests (win32 candidate paths on posix hosts). */
|
|
39
|
+
isExecutableFile?: (candidatePath: string) => boolean;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* PATH availability scan (ported from the deleted workspace openers
|
|
43
|
+
* at f858c19^, sharpened for injectability: the path module is keyed
|
|
44
|
+
* by the injected platform, and a command already carrying a known
|
|
45
|
+
* executable extension matches as-is).
|
|
46
|
+
*/
|
|
47
|
+
export declare function isOpenerCommandAvailable(command: string, options?: OpenerScanOptions): boolean;
|
|
48
|
+
export interface OpenerChoice {
|
|
49
|
+
opener: OpenerDefinition;
|
|
50
|
+
available: boolean;
|
|
51
|
+
/** `(<command> not found on PATH)` when unavailable. */
|
|
52
|
+
note: string | null;
|
|
53
|
+
}
|
|
54
|
+
/** Table order preserved, available tools first (stable sort). */
|
|
55
|
+
export declare function listOpenerChoices(table: OpenerDefinition[], options?: OpenerScanOptions): OpenerChoice[];
|
|
56
|
+
export declare function findOpener(table: OpenerDefinition[], id: string): OpenerDefinition | null;
|
|
57
|
+
export interface LaunchCommand {
|
|
58
|
+
executable: string;
|
|
59
|
+
args: string[];
|
|
60
|
+
/** The surviving primary member's path. */
|
|
61
|
+
cwd: string;
|
|
62
|
+
label: string;
|
|
63
|
+
style: OpenerStyle;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Pure argv builder. workspace-file: pre-args + the generated file's
|
|
67
|
+
* absolute path (which also defuses the cursor shim's `agent`
|
|
68
|
+
* first-arg hijack). attach-dirs: pre-args + one attach flag + path
|
|
69
|
+
* pair per surviving member, the primary included (the locked "one
|
|
70
|
+
* attach flag per member"); never a trailing positional - both agent
|
|
71
|
+
* CLIs would read one as a starter prompt, which 7.1 locks out.
|
|
72
|
+
*/
|
|
73
|
+
export declare function buildLaunchCommand(opener: OpenerDefinition, input: {
|
|
74
|
+
members: WorksetMember[];
|
|
75
|
+
codeWorkspacePath: string;
|
|
76
|
+
}): LaunchCommand;
|
|
77
|
+
//# sourceMappingURL=openers.d.ts.map
|