@we-scrum/cli 1.0.5 → 1.0.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@we-scrum/cli",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
4
4
  "description": "Cli tool for we-scrum application",
5
5
  "main": "dist/cli.js",
6
6
  "bin": {
@@ -27,29 +27,25 @@
27
27
  },
28
28
  "dependencies": {
29
29
  "@inquirer/prompts": "^8.4.2",
30
- "@modelcontextprotocol/sdk": "^1.29.0",
31
30
  "commander": "^14.0.3",
32
31
  "firebase": "^12.12.1",
33
32
  "google-auth-library": "^10.6.2",
34
33
  "handlebars": "^4.7.9",
35
- "open": "^11.0.0",
36
- "zod": "^4.4.1"
34
+ "open": "^11.0.0"
37
35
  },
38
36
  "devDependencies": {
39
- "@modelcontextprotocol/inspector": "^0.21.2",
40
37
  "@types/node": "^22.19.17",
41
38
  "tsup": "^8.5.1",
42
39
  "typescript": "^5.9.3",
43
40
  "@my-devkit/cli": "2.1.0",
44
41
  "@my-devkit/core": "1.0.0",
45
42
  "@we-scrum/commands": "1.0.0",
46
- "@we-scrum/models": "1.0.0",
47
43
  "@we-scrum/enums": "1.0.0",
44
+ "@we-scrum/models": "1.0.0",
48
45
  "@we-scrum/utils": "1.0.0"
49
46
  },
50
47
  "scripts": {
51
48
  "start": "node dist/cli.js",
52
- "inspect": "mcp-inspector node dist/cli.js mcp",
53
49
  "build": "tsup",
54
50
  "watch": "tsup --watch",
55
51
  "deploy": "pnpm build && pnpm publish --access public"
package/src/cli.ts CHANGED
@@ -5,8 +5,7 @@ import { UserProjectModel } from '@we-scrum/models';
5
5
  import { Command } from 'commander';
6
6
  import { version } from '../package.json';
7
7
 
8
- import { AuthHelper, FirebaseHelper, SettingsHelper, WeScrumHelper } from './helpers';
9
- import { startMcpServer } from './mcp-server';
8
+ import { AuthHelper, FirebaseHelper, ProjectHelper, WeScrumHelper } from './helpers';
10
9
 
11
10
  Logger.registerLogger(new Logger.ConsoleImplementation());
12
11
 
@@ -19,10 +18,16 @@ async function requireAuth(): Promise<void> {
19
18
  assert(initialized, 'Not authenticated. Please run "we-scrum login" first.');
20
19
  }
21
20
 
22
- function runCommand<T = void>(fn: (options: T) => Promise<void>) {
21
+ async function requireProject(): Promise<void> {
22
+ const root = ProjectHelper.requireRoot();
23
+ await ProjectHelper.init(root);
24
+ }
25
+
26
+ function runCommand<T = void>(fn: (options: T) => Promise<string | void>) {
23
27
  return async (options: T) => {
24
28
  try {
25
- await fn(options);
29
+ const output = await fn(options);
30
+ if (output) process.stdout.write(output + '\n');
26
31
  process.exit(0);
27
32
  } catch (error) {
28
33
  if (error?.name === 'ExitPromptError') process.exit(0);
@@ -32,6 +37,14 @@ function runCommand<T = void>(fn: (options: T) => Promise<void>) {
32
37
  };
33
38
  }
34
39
 
40
+ function runProjectCommand<T = void>(fn: (options: T) => Promise<string | void>) {
41
+ return runCommand<T>(async (options) => {
42
+ await requireProject();
43
+ await requireAuth();
44
+ return fn(options);
45
+ });
46
+ }
47
+
35
48
  program
36
49
  .command('login')
37
50
  .description('Authenticate with your Google account')
@@ -53,7 +66,7 @@ program
53
66
 
54
67
  program
55
68
  .command('use-project')
56
- .description('Select the active project')
69
+ .description('Select the active project and initialize we-scrum.json')
57
70
  .option('--projectId <id>', 'Set the active project directly without the interactive prompt')
58
71
  .action(
59
72
  runCommand<{ projectId?: string }>(async (options) => {
@@ -71,7 +84,6 @@ program
71
84
  } else {
72
85
  selectedProjectId = await select({
73
86
  message: 'Select a project',
74
- default: SettingsHelper.get().selectedProjectId,
75
87
  choices: projects.map((p) => ({
76
88
  name: p.name,
77
89
  value: p.projectId,
@@ -80,8 +92,10 @@ program
80
92
  });
81
93
  }
82
94
 
83
- SettingsHelper.set({ selectedProjectId });
84
- Logger.info(`Active project set to: ${projects.find((p) => p.projectId === selectedProjectId)?.name}`);
95
+ const projectRoot = await ProjectHelper.confirmAndCreate(selectedProjectId);
96
+ await ProjectHelper.init(projectRoot);
97
+
98
+ Logger.info(`Project configured: ${projects.find((p) => p.projectId === selectedProjectId)?.name}`);
85
99
  }),
86
100
  );
87
101
 
@@ -91,8 +105,7 @@ program
91
105
  .requiredOption('--identificationNumber <id>', 'Story identification number')
92
106
  .requiredOption('--taskId <id>', 'Unique identifier of the change to act on')
93
107
  .action(
94
- runCommand<{ identificationNumber: string; taskId: string }>(async (options) => {
95
- await requireAuth();
108
+ runProjectCommand<{ identificationNumber: string; taskId: string }>(async (options) => {
96
109
  await WeScrumHelper.startTask(options.identificationNumber, options.taskId);
97
110
  Logger.info('Task started successfully. You can now make changes to the codebase.');
98
111
  }),
@@ -104,33 +117,50 @@ program
104
117
  .requiredOption('--identificationNumber <id>', 'Story identification number')
105
118
  .requiredOption('--taskId <id>', 'Unique identifier of the change to act on')
106
119
  .action(
107
- runCommand<{ identificationNumber: string; taskId: string }>(async (options) => {
108
- await requireAuth();
120
+ runProjectCommand<{ identificationNumber: string; taskId: string }>(async (options) => {
109
121
  await WeScrumHelper.completeTask(options.identificationNumber, options.taskId);
110
122
  Logger.info('Task completed successfully.');
111
123
  }),
112
124
  );
113
125
 
114
126
  program
115
- .command('synchronize-development-policies')
116
- .description('Sync development guidelines from we-scrum into .claude/')
127
+ .command('get-story-description')
128
+ .description('Get the description of a story')
129
+ .requiredOption('--identificationNumber <id>', 'Story identification number')
117
130
  .action(
118
- runCommand(async () => {
119
- await requireAuth();
120
- const count = await WeScrumHelper.synchronizeDevelopmentPolicies(process.cwd());
121
- Logger.info(`Synchronized ${count} development ${count === 1 ? 'policy' : 'policies'}.`);
131
+ runProjectCommand<{ identificationNumber: string }>(async (options) => {
132
+ return WeScrumHelper.getStoryDescription(options.identificationNumber);
122
133
  }),
123
134
  );
124
135
 
125
136
  program
126
- .command('mcp')
127
- .description('Start the we-scrum MCP server (stdio transport)')
128
- .option('--projectId <id>', 'Override the active project for this session')
129
- .action(async (options: { projectId?: string }) => {
130
- if (options.projectId) {
131
- SettingsHelper.set({ selectedProjectId: options.projectId });
132
- }
133
- await startMcpServer();
134
- });
137
+ .command('get-story-analysis')
138
+ .description('Get the analysis DSL for a story')
139
+ .requiredOption('--identificationNumber <id>', 'Story identification number')
140
+ .action(
141
+ runProjectCommand<{ identificationNumber: string }>(async (options) => {
142
+ return WeScrumHelper.getStoryAnalysis(options.identificationNumber);
143
+ }),
144
+ );
145
+
146
+ program
147
+ .command('get-next-task')
148
+ .description('Get the next pending task for a story')
149
+ .requiredOption('--identificationNumber <id>', 'Story identification number')
150
+ .action(
151
+ runProjectCommand<{ identificationNumber: string }>(async (options) => {
152
+ return WeScrumHelper.getNextTask(options.identificationNumber);
153
+ }),
154
+ );
155
+
156
+ program
157
+ .command('synchronize-development-policies')
158
+ .description('Sync development guidelines from we-scrum into the project')
159
+ .action(
160
+ runProjectCommand(async () => {
161
+ const count = await WeScrumHelper.synchronizeDevelopmentPolicies();
162
+ Logger.info(`Synchronized ${count} development ${count === 1 ? 'policy' : 'policies'}.`);
163
+ }),
164
+ );
135
165
 
136
166
  program.parse(process.argv);
@@ -1,5 +1,6 @@
1
1
  export * from './auth-helper';
2
2
  export * from './firebase-helper';
3
+ export * from './project-helper';
3
4
  export * from './settings-helper';
4
5
  export * from './template.helper';
5
6
  export * from './we-scrum.helper';
@@ -0,0 +1,96 @@
1
+ import { assert, Logger } from '@my-devkit/core';
2
+ import { confirm, input } from '@inquirer/prompts';
3
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
4
+ import { dirname, join, resolve } from 'node:path';
5
+ import { version as cliVersion } from '../../package.json';
6
+ import { renderTemplate } from './template.helper';
7
+
8
+ const CONFIG_FILE = 'we-scrum.json';
9
+
10
+ export interface WeScrumProjectConfig {
11
+ projectId: string;
12
+ version: string;
13
+ developmentPoliciesPath: string;
14
+ }
15
+
16
+ export class ProjectHelper {
17
+ public static findRoot(from = process.cwd()): string | null {
18
+ let dir = resolve(from);
19
+ while (true) {
20
+ if (existsSync(join(dir, CONFIG_FILE))) return dir;
21
+ const parent = dirname(dir);
22
+ if (parent === dir) return null;
23
+ dir = parent;
24
+ }
25
+ }
26
+
27
+ public static requireRoot(): string {
28
+ const root = this.findRoot();
29
+ assert(!!root, `we-scrum.json not found. Please run "we-scrum use-project" first.`);
30
+ return root;
31
+ }
32
+
33
+ public static read(root: string): WeScrumProjectConfig {
34
+ const content = readFileSync(join(root, CONFIG_FILE), 'utf8');
35
+ return JSON.parse(content) as WeScrumProjectConfig;
36
+ }
37
+
38
+ public static write(root: string, partial: Partial<WeScrumProjectConfig>): void {
39
+ const existing = existsSync(join(root, CONFIG_FILE)) ? this.read(root) : ({} as WeScrumProjectConfig);
40
+ writeFileSync(join(root, CONFIG_FILE), JSON.stringify({ ...existing, ...partial }, null, 2) + '\n');
41
+ }
42
+
43
+ public static async init(root: string): Promise<void> {
44
+ const config = this.read(root);
45
+ assert(!!config.projectId, 'No project selected. Please run "we-scrum use-project" first.');
46
+ if (config.version === cliVersion) return;
47
+
48
+ this.installSkills(root);
49
+ this.write(root, { version: cliVersion });
50
+ Logger.info(`we-scrum initialized (v${cliVersion}).`);
51
+ }
52
+
53
+ public static async confirmAndCreate(projectId: string): Promise<string> {
54
+ const defaultRoot = process.cwd();
55
+
56
+ const useDefault = await confirm({
57
+ message: `Use "${defaultRoot}" as the project root?`,
58
+ default: true,
59
+ });
60
+
61
+ let root: string;
62
+ if (useDefault) {
63
+ root = defaultRoot;
64
+ } else {
65
+ root = await input({ message: 'Enter the project root directory:' });
66
+ root = resolve(root);
67
+ }
68
+
69
+ assert(existsSync(root), `Directory "${root}" does not exist.`);
70
+
71
+ const defaultPoliciesPath = '.claude/development-policies';
72
+ const confirmPolicies = await confirm({
73
+ message: `Store development policies in "${defaultPoliciesPath}"?`,
74
+ default: true,
75
+ });
76
+
77
+ const developmentPoliciesPath = confirmPolicies
78
+ ? defaultPoliciesPath
79
+ : await input({ message: 'Enter the development policies path (relative to project root):' });
80
+
81
+ this.write(root, { projectId, version: '0.0.0', developmentPoliciesPath });
82
+ return root;
83
+ }
84
+
85
+ private static installSkills(root: string): void {
86
+ if (!existsSync(join(root, '.claude'))) return;
87
+ for (const [name, template] of [
88
+ ['develop-story', 'develop-story-skill'],
89
+ ['review-analysis', 'review-analysis-skill'],
90
+ ] as const) {
91
+ const skillDir = join(root, '.claude', 'skills', name);
92
+ mkdirSync(skillDir, { recursive: true });
93
+ writeFileSync(join(skillDir, 'SKILL.md'), renderTemplate(template));
94
+ }
95
+ }
96
+ }
@@ -8,7 +8,6 @@ const SETTINGS_FILE = pathJoin(homedir(), '.we-scrum.json');
8
8
 
9
9
  export interface WeScrumSettings {
10
10
  credentials?: Credentials;
11
- selectedProjectId?: string;
12
11
  }
13
12
 
14
13
  export class SettingsHelper {
@@ -1,10 +1,12 @@
1
1
  import Handlebars from 'handlebars';
2
- import getNextTaskDescription from '../../templates/get-next-task-description.hbs';
2
+ import developStorySkill from '../../templates/develop-story-skill.hbs';
3
3
  import getNextTask from '../../templates/get-next-task.hbs';
4
+ import reviewAnalysisSkill from '../../templates/review-analysis-skill.hbs';
4
5
 
5
6
  const sources = {
7
+ 'develop-story-skill': developStorySkill,
6
8
  'get-next-task': getNextTask,
7
- 'get-next-task-description': getNextTaskDescription,
9
+ 'review-analysis-skill': reviewAnalysisSkill,
8
10
  } as const;
9
11
 
10
12
  export type TemplateName = keyof typeof sources;
@@ -11,7 +11,7 @@ import {
11
11
  TakeRouteChangeCommand,
12
12
  TakeTaskCommand,
13
13
  } from '@we-scrum/commands';
14
- import { ProgressStatus } from '@we-scrum/enums';
14
+ import { ContentType, ProgressStatus } from '@we-scrum/enums';
15
15
  import {
16
16
  DevelopmentPolicyModel,
17
17
  ProjectStoryModel,
@@ -22,30 +22,53 @@ import {
22
22
  ProjectStorySectionModelRouteChange,
23
23
  ProjectStorySectionModelTask,
24
24
  } from '@we-scrum/models';
25
- import { DevelopmentPolicyHelper } from '@we-scrum/utils';
25
+ import { AnalysisDsl, DevelopmentPolicyHelper, HtmlToTextHelper, StorySectionHelper, stringifyAnalysisDslChange } from '@we-scrum/utils';
26
26
  import { existsSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
27
27
  import { join } from 'node:path';
28
28
  import { AuthHelper } from './auth-helper';
29
29
  import { FirebaseHelper } from './firebase-helper';
30
- import { SettingsHelper } from './settings-helper';
30
+ import { ProjectHelper } from './project-helper';
31
+ import { renderTemplate } from './template.helper';
31
32
 
32
33
  export class WeScrumHelper {
33
34
  private static readonly backendUrl = 'https://europe-west1-we-scrum-prod.cloudfunctions.net';
34
- private static readonly claudeSubdir = '.claude';
35
- private static readonly relPoliciesDir = `${WeScrumHelper.claudeSubdir}/development-policies`;
36
35
  constructor(private userId: string) {}
37
36
 
38
- public static get projectId(): string {
39
- return SettingsHelper.get().selectedProjectId;
37
+ private static get projectRoot(): string {
38
+ return ProjectHelper.requireRoot();
40
39
  }
41
40
 
42
- public static assertProjectIsSelected(): void {
43
- assert(!!this.projectId, 'No active project selected. Please run "we-scrum use-project" first.');
41
+ private static get projectConfig() {
42
+ return ProjectHelper.read(this.projectRoot);
44
43
  }
45
44
 
46
- public static async getNextChange(identificationNumber: string) {
47
- this.assertProjectIsSelected();
45
+ private static get projectId(): string {
46
+ return this.projectConfig.projectId;
47
+ }
48
48
 
49
+ public static async getNextTask(identificationNumber: string): Promise<string> {
50
+ const nextChange = await this.getNextChange(identificationNumber);
51
+
52
+ if (!nextChange) {
53
+ return `## Story ${identificationNumber}\n\nAll tasks are complete.`;
54
+ }
55
+
56
+ const analysisChange = StorySectionHelper.mapSingleChange(nextChange);
57
+ const isTodo = nextChange.progress.status === ProgressStatus.ToDo;
58
+ const developmentPolicy = await this.getChangeDevelopmentPolicy(nextChange);
59
+ const { guidelinesPath, unitTestsPath } = this.getDevelopmentPolicyPaths(developmentPolicy);
60
+
61
+ return renderTemplate('get-next-task', {
62
+ taskDetails: stringifyAnalysisDslChange(analysisChange),
63
+ guidelinesPath,
64
+ unitTestsPath: developmentPolicy?.areUnitTestsMandatory ? unitTestsPath : null,
65
+ isTodo,
66
+ identificationNumber,
67
+ taskId: analysisChange.id,
68
+ });
69
+ }
70
+
71
+ public static async getNextChange(identificationNumber: string) {
49
72
  const story = await this.findStoryByIterationNumber(identificationNumber);
50
73
 
51
74
  assert(!!story, `Story "${identificationNumber}" not found in the active project.`);
@@ -60,8 +83,6 @@ export class WeScrumHelper {
60
83
  }
61
84
 
62
85
  public static async getChangeById(identificationNumber: string, changeId: string) {
63
- this.assertProjectIsSelected();
64
-
65
86
  const story = await this.findStoryByIterationNumber(identificationNumber);
66
87
  assert(!!story, `Story "${identificationNumber}" not found in the active project.`);
67
88
 
@@ -148,20 +169,39 @@ export class WeScrumHelper {
148
169
  return DevelopmentPolicyHelper.getMatchingDevelopmentPolicy(change, developmentPolicies);
149
170
  }
150
171
 
151
- public static getDevelopmentPolicyPaths(
152
- policy: DevelopmentPolicyModel | null,
153
- projectDir = '.',
154
- ): {
172
+ public static getDevelopmentPolicyPaths(policy: DevelopmentPolicyModel | null): {
155
173
  guidelinesPath: string | null;
156
174
  unitTestsPath: string | null;
157
175
  } {
158
- const policiesDir = join(projectDir, this.relPoliciesDir);
176
+ const { developmentPoliciesPath } = this.projectConfig;
177
+ const policiesDir = join(this.projectRoot, developmentPoliciesPath);
159
178
  return {
160
179
  guidelinesPath: policy?.slug ? join(policiesDir, `develop-${policy.slug}.md`) : null,
161
180
  unitTestsPath: policy?.slug ? join(policiesDir, `test-${policy.slug}.md`) : null,
162
181
  };
163
182
  }
164
183
 
184
+ public static async getStoryDescription(identificationNumber: string): Promise<string> {
185
+ const story = await this.findStoryByIterationNumber(identificationNumber);
186
+ assert(!!story, `Story "${identificationNumber}" not found in the active project.`);
187
+
188
+ const description = story.description ?? '';
189
+ if (story.descriptionContentType === ContentType.Html) {
190
+ return HtmlToTextHelper.convert(description);
191
+ }
192
+ return description;
193
+ }
194
+
195
+ public static async getStoryAnalysis(identificationNumber: string): Promise<string> {
196
+ const story = await this.findStoryByIterationNumber(identificationNumber);
197
+ assert(!!story, `Story "${identificationNumber}" not found in the active project.`);
198
+
199
+ const sections = await this.getStorySections(story.storyId);
200
+ const ast = StorySectionHelper.mapSectionsToAnalysisAst(sections);
201
+
202
+ return AnalysisDsl.stringify(ast);
203
+ }
204
+
165
205
  private static async findStoryByIterationNumber(identificationNumber: string) {
166
206
  return FirebaseHelper.findDocument<ProjectStoryModel>(`/projects/${this.projectId}/stories`, [
167
207
  ['identificationNumber', '==', identificationNumber],
@@ -176,16 +216,9 @@ export class WeScrumHelper {
176
216
  return FirebaseHelper.getCollection<DevelopmentPolicyModel>(`/projects/${this.projectId}/development-policies`);
177
217
  }
178
218
 
179
- public static async synchronizeDevelopmentPolicies(projectDir: string): Promise<number> {
180
- this.assertProjectIsSelected();
181
-
182
- const claudeDir = join(projectDir, this.claudeSubdir);
183
- assert(
184
- existsSync(claudeDir),
185
- `No ${this.claudeSubdir} directory found in ${projectDir}. Please run this command from the root of your project.`,
186
- );
187
-
188
- const policiesDir = join(projectDir, this.relPoliciesDir);
219
+ public static async synchronizeDevelopmentPolicies(): Promise<number> {
220
+ const { developmentPoliciesPath } = this.projectConfig;
221
+ const policiesDir = join(this.projectRoot, developmentPoliciesPath);
189
222
 
190
223
  rmSync(policiesDir, { recursive: true, force: true });
191
224
  mkdirSync(policiesDir, { recursive: true });
@@ -193,7 +226,7 @@ export class WeScrumHelper {
193
226
  const policies = await this.getDevelopmentPolicies();
194
227
 
195
228
  for (const policy of policies) {
196
- const { guidelinesPath, unitTestsPath } = this.getDevelopmentPolicyPaths(policy, projectDir);
229
+ const { guidelinesPath, unitTestsPath } = this.getDevelopmentPolicyPaths(policy);
197
230
  if (policy.codeGuidelines && guidelinesPath) {
198
231
  writeFileSync(guidelinesPath, policy.codeGuidelines);
199
232
  }
@@ -5,8 +5,8 @@ description: >
5
5
  Use this skill whenever the user says "develop story XXXXX", "/develop-story XXXXX",
6
6
  or asks to implement or work on a specific story number. The skill synchronizes
7
7
  development policies, then fetches and implements each task in sequence using
8
- the we-scrum MCP server and CLI.
9
- compatibility: "Requires: we-scrum MCP server, we-scrum CLI (we-scrum)"
8
+ the we-scrum CLI.
9
+ compatibility: "Requires: we-scrum CLI (we-scrum)"
10
10
  ---
11
11
 
12
12
  # Develop Story Skill
@@ -36,5 +36,11 @@ we-scrum synchronize-development-policies
36
36
 
37
37
  ## Step 2 — Fetch and implement tasks
38
38
 
39
- Call the `get_next_task` MCP tool with the story identification number and follow
40
- the instructions it returns. Repeat until all tasks are complete.
39
+ Run the following command to get the next pending task and follow the instructions it returns.
40
+ Replace `<identificationNumber>` with the story identification number.
41
+
42
+ ```bash
43
+ we-scrum get-next-task --identificationNumber <identificationNumber>
44
+ ```
45
+
46
+ Repeat until all tasks are complete.
@@ -0,0 +1,61 @@
1
+ ---
2
+ name: review-analysis
3
+ description: >
4
+ Review the analysis of a we-scrum user story. Use this skill whenever the user says
5
+ "review analysis XXXXX", "/review-analysis XXXXX", or asks to review or check the
6
+ analysis of a specific story number. The skill retrieves the story description and
7
+ analysis DSL, then reviews them and returns comments.
8
+ compatibility: "Requires: we-scrum CLI (we-scrum)"
9
+ ---
10
+
11
+ # Review Analysis Skill
12
+
13
+ Reviews the analysis of a we-scrum user story and returns comments.
14
+
15
+ ---
16
+
17
+ ## Step 0 — Resolve the story identification number
18
+
19
+ If the user provided a story identification number (e.g. `/review-analysis 00240`), use it.
20
+ Otherwise, ask: _"Which story would you like to review? Please provide the identification number."_
21
+ Do not proceed until a number is provided.
22
+
23
+ ---
24
+
25
+ ## Step 1 — Retrieve story data
26
+
27
+ Run the following commands to fetch the story description and analysis DSL.
28
+ Replace `<identificationNumber>` with the story identification number.
29
+
30
+ ```bash
31
+ we-scrum get-story-description --identificationNumber <identificationNumber>
32
+ we-scrum get-story-analysis --identificationNumber <identificationNumber>
33
+ ```
34
+
35
+ ---
36
+
37
+ ## Step 2 — Review and return comments
38
+
39
+ Review the story description and analysis DSL together. Check for:
40
+
41
+ **Completeness & correctness**
42
+ - Consistency between the description and the analysis changes
43
+ - Missing or incomplete changes relative to the described requirements
44
+ - Incorrect operations (create/update/delete used inappropriately)
45
+ - Unclear or ambiguous task descriptions
46
+
47
+ **Naming clarity**
48
+ - Object names and property names must be clear, precise, and self-explanatory
49
+ - Challenge any name that is vague, abbreviated, or could be misread — propose a better alternative
50
+ - Names should reflect the domain concept they represent, not the implementation detail
51
+
52
+ **Naming continuity**
53
+ - A name introduced in the analysis (object, property, route, enumeration) must flow consistently through the entire vertical slice: command → aggregate → event → denormalizer → model → DTO → API response
54
+ - Flag any analysis change where the proposed name would likely create a discontinuity or force a translation at any layer
55
+ - Renames or aliases between layers are a sign of a poorly chosen name — the right name should need no translation
56
+
57
+ **Migrations**
58
+ - Any change that adds, removes, or renames a field on an Aggregate or a Model must be accompanied by a migration script task in the analysis
59
+ - If such a structural change is present but no corresponding migration task exists, raise it as a blocking issue — shipping without a migration will corrupt existing data or break reads
60
+
61
+ Return your comments to the user.
@@ -1 +0,0 @@
1
- export * from './mcp-server';
@@ -1,54 +0,0 @@
1
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
- import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
4
- import { join } from 'node:path';
5
- import { z } from 'zod';
6
-
7
- import { AuthHelper, renderTemplate } from '@helpers';
8
- import developStorySkill from '../../templates/develop-story-skill.hbs';
9
- import { getNextTask } from './tools';
10
-
11
- function installSkills(): void {
12
- const skillDir = join(process.cwd(), '.claude', 'skills', 'develop-story');
13
- if (!existsSync(join(process.cwd(), '.claude'))) return;
14
- mkdirSync(skillDir, { recursive: true });
15
- writeFileSync(join(skillDir, 'SKILL.md'), developStorySkill);
16
- }
17
-
18
- export async function startMcpServer(): Promise<void> {
19
- installSkills();
20
-
21
- const initialized = await AuthHelper.initializeFromStoredCredentials();
22
- if (!initialized) {
23
- process.stderr.write('Error: Not authenticated. Please run "we-scrum login" first.\n');
24
- process.exit(1);
25
- }
26
-
27
- const server = new McpServer({ name: 'we-scrum-mcp-server', version: '1.0.0' });
28
-
29
- server.registerTool(
30
- 'get_next_task',
31
- {
32
- description: renderTemplate('get-next-task-description'),
33
- inputSchema: z.object({
34
- identificationNumber: z.string().describe('The story identification number, e.g. "144154"'),
35
- }),
36
- },
37
- async ({ identificationNumber }) => {
38
- try {
39
- const markdown = await getNextTask(identificationNumber);
40
- return { content: [{ type: 'text' as const, text: markdown }] };
41
- } catch (error) {
42
- return {
43
- content: [{ type: 'text' as const, text: JSON.stringify({ error: error.message }) }],
44
- isError: true,
45
- };
46
- }
47
- },
48
- );
49
-
50
- const transport = new StdioServerTransport();
51
- await server.connect(transport);
52
-
53
- process.stderr.write('we-scrum MCP server running.\n');
54
- }
@@ -1,26 +0,0 @@
1
- import { renderTemplate, WeScrumHelper } from '@helpers';
2
- import { ProgressStatus } from '@we-scrum/enums';
3
- import { StorySectionHelper, stringifyAnalysisDslChange } from '@we-scrum/utils';
4
-
5
- export async function getNextTask(identificationNumber: string): Promise<string> {
6
- const nextChange = await WeScrumHelper.getNextChange(identificationNumber);
7
-
8
- if (!nextChange) {
9
- return `## Story ${identificationNumber}\n\nAll tasks are complete.`;
10
- }
11
-
12
- const analysisChange = StorySectionHelper.mapSingleChange(nextChange);
13
- const isTodo = nextChange.progress.status === ProgressStatus.ToDo;
14
- const developmentPolicy = await WeScrumHelper.getChangeDevelopmentPolicy(nextChange);
15
-
16
- const { guidelinesPath, unitTestsPath } = WeScrumHelper.getDevelopmentPolicyPaths(developmentPolicy);
17
-
18
- return renderTemplate('get-next-task', {
19
- taskDetails: stringifyAnalysisDslChange(analysisChange),
20
- guidelinesPath,
21
- unitTestsPath: developmentPolicy?.areUnitTestsMandatory ? unitTestsPath : null,
22
- isTodo,
23
- identificationNumber,
24
- taskId: analysisChange.id,
25
- });
26
- }
@@ -1 +0,0 @@
1
- export * from './get-next-task';
@@ -1,3 +0,0 @@
1
- Get the next pending task for a story as a Markdown document.
2
- Returns the task description, location of development guidelines, unit test instructions,
3
- and the exact CLI commands to run (we-scrum start-task / complete-task, with taskId pre-filled).