gmc-openspec 1.4.2 → 1.4.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/cli/index.js +23 -5
- package/dist/commands/jira.js +1 -0
- package/dist/commands/workspace/context-status.js +1 -1
- package/dist/commands/workspace/open-view.js +1 -1
- package/dist/commands/workspace/operations.js +3 -3
- package/dist/commands/workspace/selection.d.ts +0 -1
- package/dist/commands/workspace/selection.js +0 -11
- package/dist/commands/workspace.d.ts +0 -1
- package/dist/commands/workspace.js +1 -14
- package/dist/core/jira/constants.d.ts +2 -0
- package/dist/core/jira/constants.js +1 -0
- package/dist/core/jira/intake.d.ts +4 -0
- package/dist/core/jira/intake.js +2 -1
- package/dist/core/jira/templates.js +21 -16
- package/dist/core/workspace/foundation.d.ts +1 -1
- package/dist/core/workspace/foundation.js +2 -2
- package/dist/core/workspace/legacy-state.js +2 -2
- package/dist/core/workspace/open-surface.d.ts +1 -1
- package/dist/core/workspace/open-surface.js +1 -1
- package/dist/core/workspace/state-io.js +4 -2
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import ora from 'ora';
|
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
6
|
import { promises as fs } from 'fs';
|
|
7
|
-
import { AI_TOOLS } from '../core/config.js';
|
|
7
|
+
import { AI_TOOLS, OPENSPEC_DIR_NAME } from '../core/config.js';
|
|
8
8
|
import { UpdateCommand } from '../core/update.js';
|
|
9
9
|
import { ListCommand } from '../core/list.js';
|
|
10
10
|
import { ArchiveCommand } from '../core/archive.js';
|
|
@@ -17,7 +17,7 @@ import { CompletionCommand } from '../commands/completion.js';
|
|
|
17
17
|
import { FeedbackCommand } from '../commands/feedback.js';
|
|
18
18
|
import { registerConfigCommand } from '../commands/config.js';
|
|
19
19
|
import { registerSchemaCommand } from '../commands/schema.js';
|
|
20
|
-
import { registerWorkspaceCommand
|
|
20
|
+
import { registerWorkspaceCommand } from '../commands/workspace.js';
|
|
21
21
|
import { registerContextStoreCommand } from '../commands/context-store.js';
|
|
22
22
|
import { registerInitiativeCommand } from '../commands/initiative.js';
|
|
23
23
|
import { findWorkspaceRoot } from '../core/workspace/index.js';
|
|
@@ -71,6 +71,21 @@ program.hook('postAction', async () => {
|
|
|
71
71
|
});
|
|
72
72
|
const availableToolIds = AI_TOOLS.filter((tool) => tool.skillsDir).map((tool) => tool.value);
|
|
73
73
|
const toolsOptionDescription = `Configure AI tools non-interactively. Use "all", "none", or a comma-separated list of: ${availableToolIds.join(', ')}`;
|
|
74
|
+
async function hasRepoLocalOpenSpecProject(projectPath) {
|
|
75
|
+
try {
|
|
76
|
+
const stats = await fs.stat(path.join(projectPath, OPENSPEC_DIR_NAME));
|
|
77
|
+
return stats.isDirectory();
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
const code = typeof error === 'object' && error !== null && 'code' in error
|
|
81
|
+
? error.code
|
|
82
|
+
: undefined;
|
|
83
|
+
if (code !== 'ENOENT' && code !== 'ENOTDIR') {
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
74
89
|
program
|
|
75
90
|
.command('init [path]')
|
|
76
91
|
.description('Initialize OpenSpec in your project')
|
|
@@ -142,12 +157,15 @@ program
|
|
|
142
157
|
.action(async (targetPath = '.', options) => {
|
|
143
158
|
try {
|
|
144
159
|
const resolvedPath = path.resolve(targetPath);
|
|
160
|
+
const updateCommand = new UpdateCommand({ force: options?.force });
|
|
161
|
+
if (await hasRepoLocalOpenSpecProject(resolvedPath)) {
|
|
162
|
+
await updateCommand.execute(resolvedPath);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
145
165
|
const workspaceRoot = await findWorkspaceRoot(resolvedPath);
|
|
146
166
|
if (workspaceRoot) {
|
|
147
|
-
|
|
148
|
-
return;
|
|
167
|
+
throw new Error('OpenSpec workspace detected. Run `openspec workspace update` to refresh workspace-local guidance and skills.');
|
|
149
168
|
}
|
|
150
|
-
const updateCommand = new UpdateCommand({ force: options?.force });
|
|
151
169
|
await updateCommand.execute(resolvedPath);
|
|
152
170
|
}
|
|
153
171
|
catch (error) {
|
package/dist/commands/jira.js
CHANGED
|
@@ -111,6 +111,7 @@ export function registerJiraCommand(program) {
|
|
|
111
111
|
issueDir: intakeResult.issueDir,
|
|
112
112
|
intakePath: intakeResult.path,
|
|
113
113
|
status,
|
|
114
|
+
documentLanguage: intakeResult.intake?.documentLanguage,
|
|
114
115
|
allowedNext: status ? getAllowedNext(status) : [],
|
|
115
116
|
nextCommands: status ? getNextCommands(status, jiraId.toUpperCase()) : [],
|
|
116
117
|
blockingStage,
|
|
@@ -25,7 +25,7 @@ export async function collectWorkspaceContextStatuses(context) {
|
|
|
25
25
|
target: 'workspace.context.store',
|
|
26
26
|
fix: context.store.selector.kind === 'registry'
|
|
27
27
|
? 'openspec context-store doctor'
|
|
28
|
-
: `Check the path in workspace.yaml or run openspec initiative show ${initiativeId} ${selector}`,
|
|
28
|
+
: `Check the path in .openspec-workspace/view.yaml or run openspec initiative show ${initiativeId} ${selector}`,
|
|
29
29
|
}),
|
|
30
30
|
];
|
|
31
31
|
}
|
|
@@ -84,7 +84,7 @@ async function resolveStoredWorkspaceInitiative(context) {
|
|
|
84
84
|
target: 'workspace.context.store',
|
|
85
85
|
fix: context.store.selector.kind === 'registry'
|
|
86
86
|
? 'openspec context-store doctor'
|
|
87
|
-
: 'Check the path in workspace.yaml.',
|
|
87
|
+
: 'Check the path in .openspec-workspace/view.yaml.',
|
|
88
88
|
});
|
|
89
89
|
}
|
|
90
90
|
}
|
|
@@ -114,7 +114,7 @@ export function validateLinkNameForCommand(name) {
|
|
|
114
114
|
function localStateInvalidStatus(error) {
|
|
115
115
|
return makeStatus('error', 'workspace_local_state_invalid', `Machine-local paths could not be read: ${asErrorMessage(error)}`, {
|
|
116
116
|
target: 'workspace.local_state',
|
|
117
|
-
fix: 'Repair workspace.yaml, then run openspec workspace relink <name> <path> for affected links.',
|
|
117
|
+
fix: 'Repair .openspec-workspace/view.yaml, then run openspec workspace relink <name> <path> for affected links.',
|
|
118
118
|
});
|
|
119
119
|
}
|
|
120
120
|
function workspaceSkillDriftStatus(workspaceName) {
|
|
@@ -279,7 +279,7 @@ export async function loadWorkspaceForDoctor(selected) {
|
|
|
279
279
|
status: [
|
|
280
280
|
makeStatus('error', 'workspace_state_invalid', `Workspace state could not be read: ${asErrorMessage(error)}`, {
|
|
281
281
|
target: 'workspace.root',
|
|
282
|
-
fix: 'Repair .openspec-workspace/
|
|
282
|
+
fix: 'Repair .openspec-workspace/view.yaml before using this workspace.',
|
|
283
283
|
}),
|
|
284
284
|
],
|
|
285
285
|
},
|
|
@@ -345,7 +345,7 @@ async function readWorkspaceViewForMutation(selected) {
|
|
|
345
345
|
catch (error) {
|
|
346
346
|
throw new WorkspaceCliError(`Workspace state could not be read: ${asErrorMessage(error)}`, 'workspace_state_invalid', {
|
|
347
347
|
target: 'workspace.state',
|
|
348
|
-
fix: 'Repair workspace.yaml before using this workspace.',
|
|
348
|
+
fix: 'Repair .openspec-workspace/view.yaml before using this workspace.',
|
|
349
349
|
});
|
|
350
350
|
}
|
|
351
351
|
}
|
|
@@ -2,7 +2,6 @@ import { type WorkspaceRegistryEntry } from '../../core/workspace/index.js';
|
|
|
2
2
|
import { SelectedWorkspace, WorkspaceSelectionOptions } from './types.js';
|
|
3
3
|
export declare function selectedWorkspaceFromEntry(entry: WorkspaceRegistryEntry): SelectedWorkspace;
|
|
4
4
|
export declare function selectedWorkspaceFromRoot(currentWorkspaceRoot: string, entries: WorkspaceRegistryEntry[]): Promise<SelectedWorkspace>;
|
|
5
|
-
export declare function selectWorkspaceRootForCommand(workspaceRoot: string): Promise<SelectedWorkspace>;
|
|
6
5
|
export declare function selectWorkspaceForCommand(options: WorkspaceSelectionOptions, commandName: string, selectionOptions?: {
|
|
7
6
|
preferPositionalName?: boolean;
|
|
8
7
|
}): Promise<SelectedWorkspace>;
|
|
@@ -44,17 +44,6 @@ export async function selectedWorkspaceFromRoot(currentWorkspaceRoot, entries) {
|
|
|
44
44
|
unregisteredCurrentWorkspace: !isKnown,
|
|
45
45
|
};
|
|
46
46
|
}
|
|
47
|
-
export async function selectWorkspaceRootForCommand(workspaceRoot) {
|
|
48
|
-
const entries = await listKnownWorkspaceEntries();
|
|
49
|
-
const currentWorkspaceRoot = await findWorkspaceRoot(workspaceRoot);
|
|
50
|
-
if (!currentWorkspaceRoot) {
|
|
51
|
-
throw new WorkspaceCliError(`No OpenSpec workspace found at '${workspaceRoot}'.`, 'workspace_not_found', {
|
|
52
|
-
target: 'workspace.root',
|
|
53
|
-
fix: 'Pass a path inside an OpenSpec workspace.',
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
return selectedWorkspaceFromRoot(currentWorkspaceRoot, entries);
|
|
57
|
-
}
|
|
58
47
|
export async function selectWorkspaceForCommand(options, commandName, selectionOptions = {}) {
|
|
59
48
|
const entries = await listKnownWorkspaceEntries();
|
|
60
49
|
if (options.workspace) {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { WorkspaceUpdateOptions } from './workspace/types.js';
|
|
3
3
|
export declare function runWorkspaceUpdate(positionalName: string | undefined, options?: WorkspaceUpdateOptions): Promise<void>;
|
|
4
|
-
export declare function runWorkspaceUpdateForRoot(workspaceRoot: string, options?: WorkspaceUpdateOptions): Promise<void>;
|
|
5
4
|
export declare function registerWorkspaceCommand(program: Command): void;
|
|
6
5
|
//# sourceMappingURL=workspace.d.ts.map
|
|
@@ -2,7 +2,7 @@ import chalk from 'chalk';
|
|
|
2
2
|
import { createWorkspaceSkillSkippedReport, generateWorkspaceAgentSkills, getWorkspaceSkillCapableTools, getWorkspaceSkillToolIds, getWorkspaceOpenerLabel, parseWorkspaceSkillToolsValue, updateWorkspaceAgentSkills, listKnownWorkspaceEntries, readWorkspaceViewState, syncWorkspaceOpenSurface, writeWorkspaceViewState, } from '../core/workspace/index.js';
|
|
3
3
|
import { isInteractive, resolveNoInteractive } from '../utils/interactive.js';
|
|
4
4
|
import { addWorkspaceLink, createManagedWorkspace, loadWorkspaceForDoctor, loadWorkspaceForList, parseSetupLinks, readWorkspaceForMutation, updateWorkspaceLink, validateWorkspaceNameForSetup, } from './workspace/operations.js';
|
|
5
|
-
import { selectWorkspaceForCommand
|
|
5
|
+
import { selectWorkspaceForCommand } from './workspace/selection.js';
|
|
6
6
|
import { launchWorkspaceOpenCommand, } from './workspace/open.js';
|
|
7
7
|
import { buildWorkspaceOpenJsonPayload, prepareWorkspaceOpen, } from './workspace/open-view.js';
|
|
8
8
|
import { getPreferredWorkspaceSkillAgentId, parseSetupOpenerOption, promptPreferredOpener, } from './workspace/opener-selection.js';
|
|
@@ -484,15 +484,6 @@ class WorkspaceCommand {
|
|
|
484
484
|
this.handleFailure(options.json, { workspace: null, workspace_skills: null, status: [] }, error);
|
|
485
485
|
}
|
|
486
486
|
}
|
|
487
|
-
async updateRoot(workspaceRoot, options = {}) {
|
|
488
|
-
try {
|
|
489
|
-
const selected = await selectWorkspaceRootForCommand(workspaceRoot);
|
|
490
|
-
await this.updateSelected(selected, options);
|
|
491
|
-
}
|
|
492
|
-
catch (error) {
|
|
493
|
-
this.handleFailure(options.json, { workspace: null, workspace_skills: null, status: [] }, error);
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
487
|
async updateSelected(selected, options) {
|
|
497
488
|
const viewState = await readWorkspaceForMutation(selected);
|
|
498
489
|
await syncWorkspaceOpenSurface(selected.root, viewState);
|
|
@@ -577,10 +568,6 @@ export async function runWorkspaceUpdate(positionalName, options = {}) {
|
|
|
577
568
|
const workspaceCommand = new WorkspaceCommand();
|
|
578
569
|
await workspaceCommand.update(positionalName, options);
|
|
579
570
|
}
|
|
580
|
-
export async function runWorkspaceUpdateForRoot(workspaceRoot, options = {}) {
|
|
581
|
-
const workspaceCommand = new WorkspaceCommand();
|
|
582
|
-
await workspaceCommand.updateRoot(workspaceRoot, options);
|
|
583
|
-
}
|
|
584
571
|
export function registerWorkspaceCommand(program) {
|
|
585
572
|
registerWorkspaceCommandWith(program, new WorkspaceCommand());
|
|
586
573
|
}
|
|
@@ -4,6 +4,8 @@ export declare const JIRA_CONFIG_VERSION = 1;
|
|
|
4
4
|
export declare const JIRA_INTAKE_SCHEMA_VERSION = 1;
|
|
5
5
|
export declare const JIRA_DEFAULT_PROVIDER = "atlassian-rovo";
|
|
6
6
|
export declare const JIRA_DEFAULT_ENDPOINT = "https://mcp.atlassian.com/v1/mcp/authv2";
|
|
7
|
+
export declare const JIRA_DOCUMENT_LANGUAGES: readonly ["en", "zh-CN"];
|
|
8
|
+
export type JiraDocumentLanguage = (typeof JIRA_DOCUMENT_LANGUAGES)[number];
|
|
7
9
|
export declare const JIRA_WORKFLOW_IDS: readonly ["jira-import", "jira-refine", "jira-review", "jira-split", "jira-propose", "jira-sync"];
|
|
8
10
|
export type JiraWorkflowId = (typeof JIRA_WORKFLOW_IDS)[number];
|
|
9
11
|
export declare const JIRA_WORKFLOW_TO_SKILL_DIR: Record<JiraWorkflowId, string>;
|
|
@@ -4,6 +4,7 @@ export const JIRA_CONFIG_VERSION = 1;
|
|
|
4
4
|
export const JIRA_INTAKE_SCHEMA_VERSION = 1;
|
|
5
5
|
export const JIRA_DEFAULT_PROVIDER = 'atlassian-rovo';
|
|
6
6
|
export const JIRA_DEFAULT_ENDPOINT = 'https://mcp.atlassian.com/v1/mcp/authv2';
|
|
7
|
+
export const JIRA_DOCUMENT_LANGUAGES = ['en', 'zh-CN'];
|
|
7
8
|
export const JIRA_WORKFLOW_IDS = [
|
|
8
9
|
'jira-import',
|
|
9
10
|
'jira-refine',
|
|
@@ -13,6 +13,10 @@ export declare const JiraIntakeSchema: z.ZodObject<{
|
|
|
13
13
|
proposed: "proposed";
|
|
14
14
|
synced: "synced";
|
|
15
15
|
}>>;
|
|
16
|
+
documentLanguage: z.ZodDefault<z.ZodEnum<{
|
|
17
|
+
en: "en";
|
|
18
|
+
"zh-CN": "zh-CN";
|
|
19
|
+
}>>;
|
|
16
20
|
sourceHashSha256: z.ZodOptional<z.ZodString>;
|
|
17
21
|
allowedNext: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
18
22
|
split: "split";
|
package/dist/core/jira/intake.js
CHANGED
|
@@ -2,13 +2,14 @@ import { existsSync, readFileSync } from 'fs';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { parse as parseYaml } from 'yaml';
|
|
4
4
|
import { z } from 'zod';
|
|
5
|
-
import { JIRA_ALLOWED_NEXT, JIRA_INTAKE_SCHEMA_VERSION, JIRA_STATUS_ORDER, } from './constants.js';
|
|
5
|
+
import { JIRA_ALLOWED_NEXT, JIRA_DOCUMENT_LANGUAGES, JIRA_INTAKE_SCHEMA_VERSION, JIRA_STATUS_ORDER, } from './constants.js';
|
|
6
6
|
import { getJiraIssueDir, normalizeJiraId } from './paths.js';
|
|
7
7
|
export const JiraIntakeSchema = z
|
|
8
8
|
.object({
|
|
9
9
|
schemaVersion: z.number().int().positive().default(JIRA_INTAKE_SCHEMA_VERSION),
|
|
10
10
|
jira: z.string().min(1),
|
|
11
11
|
status: z.enum(JIRA_STATUS_ORDER).default('imported'),
|
|
12
|
+
documentLanguage: z.enum(JIRA_DOCUMENT_LANGUAGES).default('en'),
|
|
12
13
|
sourceHashSha256: z.string().optional(),
|
|
13
14
|
allowedNext: z.array(z.enum(JIRA_STATUS_ORDER)).optional(),
|
|
14
15
|
})
|
|
@@ -5,6 +5,8 @@ const SHARED_GUARDRAILS = `
|
|
|
5
5
|
- Treat Jira as the business input source, not the final engineering specification.
|
|
6
6
|
- Never create or modify \`openspec/changes/*\` directly from raw Jira description or comments.
|
|
7
7
|
- Never modify \`source.md\`; it is a read-only Jira snapshot protected by \`sourceHashSha256\` in \`intake.yaml\`.
|
|
8
|
+
- Use \`intake.yaml\` \`documentLanguage\` for generated prose documents: \`en\` means English and \`zh-CN\` means Simplified Chinese.
|
|
9
|
+
- Keep structured YAML fields, status enum values, mapping keys, story IDs, requirement IDs, and traceability keys as stable English/code values regardless of \`documentLanguage\`.
|
|
8
10
|
- Blocking questions must be answered before requirements can be marked reviewed.
|
|
9
11
|
- Assumptions must be explicit in \`assumptions.md\` and approved before story split or OpenSpec proposal.
|
|
10
12
|
- Keep \`mapping.yaml\` current so Jira AC maps to requirements, stories, and OpenSpec changes.
|
|
@@ -28,13 +30,16 @@ ${SHARED_GUARDRAILS}
|
|
|
28
30
|
- Read summary, description, acceptance criteria, comments, linked issues, labels, components, and attachments metadata where available.
|
|
29
31
|
- Use \`getJiraIssueRemoteIssueLinks\` and \`searchJiraIssuesUsingJql\` only when linked issues or related ticket lookup is needed.
|
|
30
32
|
- Do not write to Jira.
|
|
31
|
-
2.
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
2. Ask the user which document language to use for subsequent generated documents: English or Simplified Chinese. Default to English when the user has no preference.
|
|
34
|
+
- Record English as \`documentLanguage: en\`.
|
|
35
|
+
- Record Simplified Chinese as \`documentLanguage: zh-CN\`.
|
|
36
|
+
3. Create \`jira/<JIRA-ID>-<slug>/\` using a short slug from the Jira summary.
|
|
37
|
+
4. Write \`source.md\` as an auditable snapshot with field names and source sections. Preserve the original Jira text and do not translate \`source.md\`.
|
|
38
|
+
5. Compute SHA-256 of \`source.md\` and write \`intake.yaml\`:
|
|
39
|
+
\`schemaVersion: 1\`, \`jira\`, \`status: imported\`, \`documentLanguage\`, \`sourceHashSha256\`.
|
|
40
|
+
6. Write \`requirement-draft.md\` as a concise product requirement draft using \`documentLanguage\`.
|
|
41
|
+
7. Write initial \`questions.md\` and \`assumptions.md\` using \`documentLanguage\`, plus \`mapping.yaml\` placeholders with stable English/code keys.
|
|
42
|
+
8. Run \`openspec jira validate <JIRA-ID> --stage source\` and report the result.
|
|
38
43
|
|
|
39
44
|
## Output
|
|
40
45
|
|
|
@@ -52,14 +57,14 @@ ${SHARED_GUARDRAILS}
|
|
|
52
57
|
|
|
53
58
|
## Steps
|
|
54
59
|
|
|
55
|
-
1. Run \`openspec jira status <JIRA-ID> --json\` and read the intake folder.
|
|
60
|
+
1. Run \`openspec jira status <JIRA-ID> --json\` and read the intake folder, including \`intake.yaml\` \`documentLanguage\`.
|
|
56
61
|
2. Read \`source.md\`, \`requirement-draft.md\`, \`questions.md\`, \`assumptions.md\`, and \`mapping.yaml\`.
|
|
57
62
|
3. Identify ambiguity as either:
|
|
58
63
|
- **Blocking**: affects scope, AC, permissions, data contract, external behavior, or story boundaries.
|
|
59
64
|
- **Non-Blocking**: can be captured as an explicit assumption.
|
|
60
|
-
4. Ask the user all blocking questions before proceeding. Update \`questions.md\` with statuses
|
|
61
|
-
5. Record non-blocking assumptions in \`assumptions.md\`; do not hide assumptions in requirement prose.
|
|
62
|
-
6. When blocking questions are resolved, write \`requirement.md\` and set \`intake.yaml\` status to \`ready-for-review\`.
|
|
65
|
+
4. Ask the user all blocking questions before proceeding. Update \`questions.md\` with statuses using \`documentLanguage\`.
|
|
66
|
+
5. Record non-blocking assumptions in \`assumptions.md\` using \`documentLanguage\`; do not hide assumptions in requirement prose.
|
|
67
|
+
6. When blocking questions are resolved, write \`requirement.md\` using \`documentLanguage\` and set \`intake.yaml\` status to \`ready-for-review\`.
|
|
63
68
|
7. Run \`openspec jira validate <JIRA-ID> --stage requirement\`.
|
|
64
69
|
|
|
65
70
|
## Output
|
|
@@ -78,7 +83,7 @@ ${SHARED_GUARDRAILS}
|
|
|
78
83
|
|
|
79
84
|
## Steps
|
|
80
85
|
|
|
81
|
-
1. Read \`requirement.md\`, \`questions.md\`, and \`assumptions.md\`.
|
|
86
|
+
1. Read \`intake.yaml\` to get \`documentLanguage\`, then read \`requirement.md\`, \`questions.md\`, and \`assumptions.md\`.
|
|
82
87
|
2. Run \`openspec jira validate <JIRA-ID> --stage requirement --json\`.
|
|
83
88
|
3. If validation fails, explain the blockers and return to \`/opsx:jira-refine\`.
|
|
84
89
|
4. Ask the user to approve or request edits to the requirement and assumptions.
|
|
@@ -105,8 +110,8 @@ Each story must have independent review value, clear AC, scope, non-goals, depen
|
|
|
105
110
|
## Steps
|
|
106
111
|
|
|
107
112
|
1. Run \`openspec jira validate <JIRA-ID> --stage requirement --json\`.
|
|
108
|
-
2. Read \`requirement.md\`, \`mapping.yaml\`, and existing OpenSpec specs if capability choices are unclear.
|
|
109
|
-
3. Write \`stories.md\` with story ids, titles, scope, non-goals, AC, dependencies, risks, and suggested OpenSpec change names.
|
|
113
|
+
2. Read \`intake.yaml\` to get \`documentLanguage\`, plus \`requirement.md\`, \`mapping.yaml\`, and existing OpenSpec specs if capability choices are unclear.
|
|
114
|
+
3. Write \`stories.md\` using \`documentLanguage\`, with stable English/code story ids, titles, scope, non-goals, AC, dependencies, risks, and suggested OpenSpec change names.
|
|
110
115
|
4. Update \`mapping.yaml\` so every Jira AC maps to at least one story.
|
|
111
116
|
5. Set \`intake.yaml\` status to \`split\` and ask the user to review the story split.
|
|
112
117
|
6. After explicit user approval, set status to \`stories-reviewed\` and run \`openspec jira validate <JIRA-ID> --stage stories\`.
|
|
@@ -128,13 +133,13 @@ ${SHARED_GUARDRAILS}
|
|
|
128
133
|
## Steps
|
|
129
134
|
|
|
130
135
|
1. Run \`openspec jira validate <JIRA-ID> --stage stories --json\`. Stop if it fails.
|
|
131
|
-
2. Read \`stories.md\`, \`mapping.yaml\`, and \`requirement.md\`.
|
|
136
|
+
2. Read \`intake.yaml\` to get \`documentLanguage\`, plus \`stories.md\`, \`mapping.yaml\`, and \`requirement.md\`.
|
|
132
137
|
3. For each non-deferred reviewed story, create exactly one OpenSpec change:
|
|
133
138
|
\`openspec new change <jira-id>-<story-slug>\`.
|
|
134
139
|
4. Generate artifacts through the existing OpenSpec flow:
|
|
135
140
|
- Run \`openspec status --change <change> --json\`.
|
|
136
141
|
- For each ready artifact, run \`openspec instructions <artifact> --change <change> --json\`.
|
|
137
|
-
- Create \`proposal.md\`, delta specs, \`design.md\`, and \`tasks.md\` from the reviewed story and requirement, not raw Jira text.
|
|
142
|
+
- Create \`proposal.md\`, delta specs, \`design.md\`, and \`tasks.md\` using \`documentLanguage\` from the reviewed story and requirement, not raw Jira text.
|
|
138
143
|
5. Update \`mapping.yaml\` so every story records its OpenSpec change and spec files.
|
|
139
144
|
6. Set \`intake.yaml\` status to \`proposed\` and run \`openspec jira validate <JIRA-ID> --stage propose\`.
|
|
140
145
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type ContextStoreBinding, type ContextStoreSelector } from '../context-store/index.js';
|
|
2
2
|
export declare const WORKSPACE_METADATA_DIR_NAME = ".openspec-workspace";
|
|
3
|
-
export declare const WORKSPACE_VIEW_STATE_FILE_NAME = "
|
|
3
|
+
export declare const WORKSPACE_VIEW_STATE_FILE_NAME = "view.yaml";
|
|
4
4
|
export declare const WORKSPACE_CHANGES_DIR_NAME = "changes";
|
|
5
5
|
export declare const WORKSPACE_CODE_WORKSPACE_EXTENSION = ".code-workspace";
|
|
6
6
|
export declare const WORKSPACE_SUPPORTED_OPENER_VALUES: readonly ["codex-cli", "claude", "github-copilot", "editor"];
|
|
@@ -3,7 +3,7 @@ import { z } from 'zod';
|
|
|
3
3
|
import { normalizeContextStoreBinding, } from '../context-store/index.js';
|
|
4
4
|
import { FileSystemUtils } from '../../utils/file-system.js';
|
|
5
5
|
export const WORKSPACE_METADATA_DIR_NAME = '.openspec-workspace';
|
|
6
|
-
export const WORKSPACE_VIEW_STATE_FILE_NAME = '
|
|
6
|
+
export const WORKSPACE_VIEW_STATE_FILE_NAME = 'view.yaml';
|
|
7
7
|
export const WORKSPACE_CHANGES_DIR_NAME = 'changes';
|
|
8
8
|
export const WORKSPACE_CODE_WORKSPACE_EXTENSION = '.code-workspace';
|
|
9
9
|
export const WORKSPACE_SUPPORTED_OPENER_VALUES = [
|
|
@@ -25,7 +25,7 @@ export function getWorkspaceMetadataDir(workspaceRoot) {
|
|
|
25
25
|
return joinWorkspacePath(workspaceRoot, WORKSPACE_METADATA_DIR_NAME);
|
|
26
26
|
}
|
|
27
27
|
export function getWorkspaceViewStatePath(workspaceRoot) {
|
|
28
|
-
return joinWorkspacePath(workspaceRoot, WORKSPACE_VIEW_STATE_FILE_NAME);
|
|
28
|
+
return joinWorkspacePath(getWorkspaceMetadataDir(workspaceRoot), WORKSPACE_VIEW_STATE_FILE_NAME);
|
|
29
29
|
}
|
|
30
30
|
export function getWorkspaceChangesDir(workspaceRoot) {
|
|
31
31
|
return joinWorkspacePath(workspaceRoot, WORKSPACE_CHANGES_DIR_NAME);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
-
import { WORKSPACE_METADATA_DIR_NAME,
|
|
3
|
+
import { WORKSPACE_METADATA_DIR_NAME, getWorkspaceMetadataDir, parseWorkspaceViewState, validateWorkspaceLinkName, validateWorkspaceName, validateWorkspacePreferredOpener, } from './foundation.js';
|
|
4
4
|
import { FileSystemUtils } from '../../utils/file-system.js';
|
|
5
|
-
export const WORKSPACE_LEGACY_SHARED_STATE_FILE_NAME =
|
|
5
|
+
export const WORKSPACE_LEGACY_SHARED_STATE_FILE_NAME = 'workspace.yaml';
|
|
6
6
|
export const WORKSPACE_LEGACY_LOCAL_STATE_FILE_NAME = 'local.yaml';
|
|
7
7
|
export const WORKSPACE_LEGACY_LOCAL_STATE_IGNORE_PATTERN = `${WORKSPACE_METADATA_DIR_NAME}/${WORKSPACE_LEGACY_LOCAL_STATE_FILE_NAME}`;
|
|
8
8
|
function joinWorkspacePath(basePath, ...segments) {
|
|
@@ -3,7 +3,7 @@ export declare const WORKSPACE_GUIDANCE_START_MARKER = "<!-- OPENSPEC:WORKSPACE-
|
|
|
3
3
|
export declare const WORKSPACE_GUIDANCE_END_MARKER = "<!-- OPENSPEC:WORKSPACE-GUIDANCE:END -->";
|
|
4
4
|
export declare const WORKSPACE_OPEN_ROOT_FOLDER_LABEL = "OpenSpec workspace";
|
|
5
5
|
export declare const WORKSPACE_OPEN_INITIATIVE_FOLDER_LABEL = "Initiative context";
|
|
6
|
-
export declare const WORKSPACE_GUIDANCE_BODY = "# OpenSpec Workspace Guidance\n\nThis directory is an OpenSpec workspace: a local working view over context stores, initiatives, repos, and folders.\n\n- Use this workspace to open the local view of coordinated work.\n- Use initiatives for durable cross-team or cross-repo intent, decisions, requirements, and coordination context.\n- Use repo-local OpenSpec changes for implementation plans owned by a repo or team.\n- Use linked repos and folders to inspect context, understand ownership, and make edits in the place that owns the work.\n- Keep workspace-local files focused on local paths, opener state, agent setup, and other machine-specific view state.\n- Use OpenSpec workspace commands instead of hand-editing
|
|
6
|
+
export declare const WORKSPACE_GUIDANCE_BODY = "# OpenSpec Workspace Guidance\n\nThis directory is an OpenSpec workspace: a local working view over context stores, initiatives, repos, and folders.\n\n- Use this workspace to open the local view of coordinated work.\n- Use initiatives for durable cross-team or cross-repo intent, decisions, requirements, and coordination context.\n- Use repo-local OpenSpec changes for implementation plans owned by a repo or team.\n- Use linked repos and folders to inspect context, understand ownership, and make edits in the place that owns the work.\n- Keep workspace-local files focused on local paths, opener state, agent setup, and other machine-specific view state.\n- Use OpenSpec workspace commands instead of hand-editing `.openspec-workspace/view.yaml`.\n- If this workspace contains legacy or beta workspace-level planning files, treat them as compatibility context unless the user explicitly asks to use that beta flow.";
|
|
7
7
|
export interface WorkspaceOpenResolvedContext {
|
|
8
8
|
contextStore: {
|
|
9
9
|
id: string;
|
|
@@ -16,7 +16,7 @@ This directory is an OpenSpec workspace: a local working view over context store
|
|
|
16
16
|
- Use repo-local OpenSpec changes for implementation plans owned by a repo or team.
|
|
17
17
|
- Use linked repos and folders to inspect context, understand ownership, and make edits in the place that owns the work.
|
|
18
18
|
- Keep workspace-local files focused on local paths, opener state, agent setup, and other machine-specific view state.
|
|
19
|
-
- Use OpenSpec workspace commands instead of hand-editing
|
|
19
|
+
- Use OpenSpec workspace commands instead of hand-editing \`.openspec-workspace/view.yaml\`.
|
|
20
20
|
- If this workspace contains legacy or beta workspace-level planning files, treat them as compatibility context unless the user explicitly asks to use that beta flow.`;
|
|
21
21
|
async function fileExists(filePath) {
|
|
22
22
|
try {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as nodeFs from 'node:fs';
|
|
2
2
|
import * as path from 'node:path';
|
|
3
3
|
import { FileSystemUtils } from '../../utils/file-system.js';
|
|
4
|
-
import { getWorkspaceChangesDir, getWorkspaceViewStatePath, parseWorkspaceViewState, serializeWorkspaceViewState, } from './foundation.js';
|
|
4
|
+
import { getWorkspaceChangesDir, getWorkspaceMetadataDir, getWorkspaceViewStatePath, parseWorkspaceViewState, serializeWorkspaceViewState, } from './foundation.js';
|
|
5
5
|
import { getWorkspaceLegacyLocalStatePath, getWorkspaceLegacySharedStatePath, parseWorkspaceLocalState, parseWorkspaceSharedState, workspaceStatePartsToViewState, } from './legacy-state.js';
|
|
6
6
|
const fs = nodeFs.promises;
|
|
7
7
|
async function pathIsFile(filePath) {
|
|
@@ -111,7 +111,9 @@ export async function readOptionalWorkspaceViewState(workspaceRoot) {
|
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
113
|
export async function writeWorkspaceViewState(workspaceRoot, state) {
|
|
114
|
-
|
|
114
|
+
const content = serializeWorkspaceViewState(state);
|
|
115
|
+
await FileSystemUtils.createDirectory(getWorkspaceMetadataDir(workspaceRoot));
|
|
116
|
+
await FileSystemUtils.writeFile(getWorkspaceViewStatePath(workspaceRoot), content);
|
|
115
117
|
}
|
|
116
118
|
export async function workspaceChangesDirExists(workspaceRoot) {
|
|
117
119
|
return pathIsDirectory(getWorkspaceChangesDir(workspaceRoot));
|