@thiagodiogo/pscode 1.0.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/bin/pscode.js +0 -0
- package/dist/cli/index.js +6 -7
- package/dist/commands/config.d.ts +4 -12
- package/dist/commands/config.js +69 -242
- package/dist/core/change-metadata/schema.d.ts +1 -0
- package/dist/core/change-metadata/schema.js +1 -0
- package/dist/core/{archive.d.ts → complete.d.ts} +2 -3
- package/dist/core/{archive.js → complete.js} +63 -64
- package/dist/core/completions/command-registry.js +5 -9
- package/dist/core/config-schema.d.ts +1 -5
- package/dist/core/config-schema.js +2 -5
- package/dist/core/global-config.d.ts +1 -3
- package/dist/core/global-config.js +1 -1
- package/dist/core/init.d.ts +2 -0
- package/dist/core/init.js +81 -21
- package/dist/core/jira-transition.d.ts +16 -0
- package/dist/core/jira-transition.js +29 -0
- package/dist/core/migration.d.ts +3 -12
- package/dist/core/migration.js +10 -72
- package/dist/core/presets/dixi.d.ts +32 -0
- package/dist/core/presets/dixi.js +405 -0
- package/dist/core/profile-sync-drift.js +9 -2
- package/dist/core/profiles.d.ts +23 -21
- package/dist/core/profiles.js +28 -25
- package/dist/core/shared/skill-generation.js +3 -5
- package/dist/core/shared/tool-detection.d.ts +2 -2
- package/dist/core/shared/tool-detection.js +1 -3
- package/dist/core/templates/skill-templates.d.ts +1 -2
- package/dist/core/templates/skill-templates.js +1 -2
- package/dist/core/templates/workflows/apply-change.js +3 -3
- package/dist/core/templates/workflows/archive-change.d.ts +2 -2
- package/dist/core/templates/workflows/archive-change.js +10 -10
- package/dist/core/templates/workflows/onboard.js +9 -9
- package/dist/core/update.d.ts +1 -6
- package/dist/core/update.js +5 -29
- package/dist/core/workspace/foundation.d.ts +1 -1
- package/dist/core/workspace/foundation.js +1 -1
- package/dist/core/workspace/legacy-state.js +1 -1
- package/dist/core/workspace/skills.d.ts +4 -3
- package/dist/core/workspace/skills.js +3 -3
- package/package.json +21 -22
- package/pscode/content/dixi/architectures/feature-sliced-react/eslint-architecture.mjs.template +44 -0
- package/pscode/content/dixi/architectures/feature-sliced-react/features/README.md.template +30 -0
- package/pscode/content/dixi/architectures/feature-sliced-react/skeleton.yaml +8 -0
- package/pscode/content/dixi/architectures/hexagonal-spring/ArchitectureTest.java.template +41 -0
- package/pscode/content/dixi/architectures/hexagonal-spring/skeleton.yaml +11 -0
- package/pscode/content/dixi/claude-runtime/CLAUDE.md.java.template +62 -0
- package/pscode/content/dixi/claude-runtime/CLAUDE.md.react.template +74 -0
- package/pscode/content/dixi/claude-runtime/commands/adr.md +75 -0
- package/pscode/content/dixi/claude-runtime/commands/arch-check.md +64 -0
- package/pscode/content/dixi/claude-runtime/commands/dod.md +66 -0
- package/pscode/content/dixi/claude-runtime/commands/jira-draft.md +80 -0
- package/pscode/content/dixi/claude-runtime/commands/jira-setup.md +105 -0
- package/pscode/content/dixi/claude-runtime/commands/jira-sync.md +69 -0
- package/pscode/content/dixi/claude-runtime/commands/rfc.md +73 -0
- package/pscode/content/dixi/claude-runtime/hooks/arch-guard.mjs +101 -0
- package/pscode/content/dixi/claude-runtime/hooks/jira-context.mjs +60 -0
- package/pscode/content/dixi/claude-runtime/skills/pstld-arch-guardian.md +101 -0
- package/pscode/content/dixi/claude-runtime/skills/pstld-commit-crafter.md +98 -0
- package/pscode/content/dixi/claude-runtime/skills/pstld-jira-context.md +64 -0
- package/pscode/content/dixi/context/java/architecture.md +143 -0
- package/pscode/content/dixi/context/java/naming.md +62 -0
- package/pscode/content/dixi/context/java/testing.md +162 -0
- package/pscode/content/dixi/context/react/architecture.md +119 -0
- package/pscode/content/dixi/context/react/naming.md +129 -0
- package/pscode/content/dixi/context/react/testing.md +141 -0
- package/pscode/content/dixi/context/shared/commits.md +47 -0
- package/pscode/content/dixi/context/shared/dev-flow.md +53 -0
- package/pscode/content/dixi/context/shared/dod.md +38 -0
- package/pscode/content/dixi/context/shared/pr-flow.md +53 -0
- package/pscode/content/dixi/kit/java/.editorconfig +25 -0
- package/pscode/content/dixi/kit/java/.github/workflows/ci-java.yml +68 -0
- package/pscode/content/dixi/kit/java/.husky/commit-msg +2 -0
- package/pscode/content/dixi/kit/react/.editorconfig +20 -0
- package/pscode/content/dixi/kit/react/.github/workflows/ci-react.yml +80 -0
- package/pscode/content/dixi/kit/react/.husky/commit-msg +2 -0
- package/pscode/content/dixi/kit/react/.husky/pre-commit +2 -0
- package/pscode/content/dixi/kit/react/lint-staged.config.mjs +4 -0
- package/pscode/content/dixi/kit/shared/.commitlintrc.yml +15 -0
- package/pscode/content/dixi/kit/shared/.github/pull_request_template.md +24 -0
- package/schemas/pstld-workflow/schema.yaml +67 -0
- package/schemas/pstld-workflow/templates/design.md +15 -0
- package/schemas/pstld-workflow/templates/rfc.md +26 -0
- package/schemas/pstld-workflow/templates/tasks.md +15 -0
- package/dist/core/templates/workflows/sync-specs.d.ts +0 -10
- package/dist/core/templates/workflows/sync-specs.js +0 -290
|
@@ -4,6 +4,8 @@ import { getTaskProgressForChange, formatTaskStatus } from '../utils/task-progre
|
|
|
4
4
|
import { Validator } from './validation/validator.js';
|
|
5
5
|
import chalk from 'chalk';
|
|
6
6
|
import { findSpecUpdates, buildUpdatedSpec, writeUpdatedSpec, } from './specs-apply.js';
|
|
7
|
+
import { readChangeMetadata } from '../utils/change-metadata.js';
|
|
8
|
+
import { readJiraConfig, tryTransitionJiraIssue } from './jira-transition.js';
|
|
7
9
|
/**
|
|
8
10
|
* Recursively copy a directory. Used when fs.rename fails (e.g. EPERM on Windows).
|
|
9
11
|
*/
|
|
@@ -42,7 +44,7 @@ async function moveDirectory(src, dest) {
|
|
|
42
44
|
}
|
|
43
45
|
}
|
|
44
46
|
}
|
|
45
|
-
export class
|
|
47
|
+
export class CompleteCommand {
|
|
46
48
|
async execute(changeName, options = {}) {
|
|
47
49
|
const targetPath = '.';
|
|
48
50
|
const changesDir = path.join(targetPath, 'pscode', 'changes');
|
|
@@ -149,7 +151,7 @@ export class ArchiveCommand {
|
|
|
149
151
|
default: false
|
|
150
152
|
});
|
|
151
153
|
if (!proceed) {
|
|
152
|
-
console.log('
|
|
154
|
+
console.log('Complete cancelled.');
|
|
153
155
|
return;
|
|
154
156
|
}
|
|
155
157
|
}
|
|
@@ -172,7 +174,7 @@ export class ArchiveCommand {
|
|
|
172
174
|
default: false
|
|
173
175
|
});
|
|
174
176
|
if (!proceed) {
|
|
175
|
-
console.log('
|
|
177
|
+
console.log('Complete cancelled.');
|
|
176
178
|
return;
|
|
177
179
|
}
|
|
178
180
|
}
|
|
@@ -180,73 +182,49 @@ export class ArchiveCommand {
|
|
|
180
182
|
console.log(`Warning: ${incompleteTasks} incomplete task(s) found. Continuing due to --yes flag.`);
|
|
181
183
|
}
|
|
182
184
|
}
|
|
183
|
-
//
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
if (specUpdates.length > 0) {
|
|
191
|
-
console.log('\nSpecs to update:');
|
|
185
|
+
// Sync delta specs to main specs
|
|
186
|
+
const specUpdates = await findSpecUpdates(changeDir, mainSpecsDir);
|
|
187
|
+
if (specUpdates.length > 0) {
|
|
188
|
+
console.log('\nSincronizando specs...');
|
|
189
|
+
// Prepare all updates first (validation pass, no writes)
|
|
190
|
+
const prepared = [];
|
|
191
|
+
try {
|
|
192
192
|
for (const update of specUpdates) {
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
console.log(` ${capability}: ${status}`);
|
|
196
|
-
}
|
|
197
|
-
let shouldUpdateSpecs = true;
|
|
198
|
-
if (!options.yes) {
|
|
199
|
-
const { confirm } = await import('@inquirer/prompts');
|
|
200
|
-
shouldUpdateSpecs = await confirm({
|
|
201
|
-
message: 'Proceed with spec updates?',
|
|
202
|
-
default: true
|
|
203
|
-
});
|
|
204
|
-
if (!shouldUpdateSpecs) {
|
|
205
|
-
console.log('Skipping spec updates. Proceeding with archive.');
|
|
206
|
-
}
|
|
193
|
+
const built = await buildUpdatedSpec(update, changeName);
|
|
194
|
+
prepared.push({ update, rebuilt: built.rebuilt, counts: built.counts });
|
|
207
195
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
196
|
+
}
|
|
197
|
+
catch (err) {
|
|
198
|
+
console.log(String(err.message || err));
|
|
199
|
+
console.log('Aborted. No files were changed.');
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
// All validations passed; pre-validate rebuilt full spec and then write files and display counts
|
|
203
|
+
let totals = { added: 0, modified: 0, removed: 0, renamed: 0 };
|
|
204
|
+
for (const p of prepared) {
|
|
205
|
+
const specName = path.basename(path.dirname(p.update.target));
|
|
206
|
+
if (!skipValidation) {
|
|
207
|
+
const report = await new Validator().validateSpecContent(specName, p.rebuilt);
|
|
208
|
+
if (!report.valid) {
|
|
209
|
+
console.log(chalk.red(`\nValidation errors in rebuilt spec for ${specName} (will not write changes):`));
|
|
210
|
+
for (const issue of report.issues) {
|
|
211
|
+
if (issue.level === 'ERROR')
|
|
212
|
+
console.log(chalk.red(` ✗ ${issue.message}`));
|
|
213
|
+
else if (issue.level === 'WARNING')
|
|
214
|
+
console.log(chalk.yellow(` ⚠ ${issue.message}`));
|
|
215
215
|
}
|
|
216
|
-
}
|
|
217
|
-
catch (err) {
|
|
218
|
-
console.log(String(err.message || err));
|
|
219
216
|
console.log('Aborted. No files were changed.');
|
|
220
217
|
return;
|
|
221
218
|
}
|
|
222
|
-
// All validations passed; pre-validate rebuilt full spec and then write files and display counts
|
|
223
|
-
let totals = { added: 0, modified: 0, removed: 0, renamed: 0 };
|
|
224
|
-
for (const p of prepared) {
|
|
225
|
-
const specName = path.basename(path.dirname(p.update.target));
|
|
226
|
-
if (!skipValidation) {
|
|
227
|
-
const report = await new Validator().validateSpecContent(specName, p.rebuilt);
|
|
228
|
-
if (!report.valid) {
|
|
229
|
-
console.log(chalk.red(`\nValidation errors in rebuilt spec for ${specName} (will not write changes):`));
|
|
230
|
-
for (const issue of report.issues) {
|
|
231
|
-
if (issue.level === 'ERROR')
|
|
232
|
-
console.log(chalk.red(` ✗ ${issue.message}`));
|
|
233
|
-
else if (issue.level === 'WARNING')
|
|
234
|
-
console.log(chalk.yellow(` ⚠ ${issue.message}`));
|
|
235
|
-
}
|
|
236
|
-
console.log('Aborted. No files were changed.');
|
|
237
|
-
return;
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
await writeUpdatedSpec(p.update, p.rebuilt, p.counts);
|
|
241
|
-
totals.added += p.counts.added;
|
|
242
|
-
totals.modified += p.counts.modified;
|
|
243
|
-
totals.removed += p.counts.removed;
|
|
244
|
-
totals.renamed += p.counts.renamed;
|
|
245
|
-
}
|
|
246
|
-
console.log(`Totals: + ${totals.added}, ~ ${totals.modified}, - ${totals.removed}, → ${totals.renamed}`);
|
|
247
|
-
console.log('Specs updated successfully.');
|
|
248
219
|
}
|
|
220
|
+
await writeUpdatedSpec(p.update, p.rebuilt, p.counts);
|
|
221
|
+
totals.added += p.counts.added;
|
|
222
|
+
totals.modified += p.counts.modified;
|
|
223
|
+
totals.removed += p.counts.removed;
|
|
224
|
+
totals.renamed += p.counts.renamed;
|
|
249
225
|
}
|
|
226
|
+
console.log(`Totals: + ${totals.added}, ~ ${totals.modified}, - ${totals.removed}, → ${totals.renamed}`);
|
|
227
|
+
console.log('Specs updated successfully.');
|
|
250
228
|
}
|
|
251
229
|
// Create archive directory with date prefix
|
|
252
230
|
const archiveName = `${this.getArchiveDate()}-${changeName}`;
|
|
@@ -263,6 +241,27 @@ export class ArchiveCommand {
|
|
|
263
241
|
}
|
|
264
242
|
// Create archive directory if needed
|
|
265
243
|
await fs.mkdir(archiveDir, { recursive: true });
|
|
244
|
+
// Attempt JIRA transition before moving (non-fatal)
|
|
245
|
+
try {
|
|
246
|
+
const changeMetadata = readChangeMetadata(changeDir, targetPath);
|
|
247
|
+
if (changeMetadata?.jiraIssueKey) {
|
|
248
|
+
const jiraConfig = await readJiraConfig(targetPath);
|
|
249
|
+
const transitionId = jiraConfig?.transitions?.done;
|
|
250
|
+
if (transitionId) {
|
|
251
|
+
const result = await tryTransitionJiraIssue(changeMetadata.jiraIssueKey, transitionId);
|
|
252
|
+
if (result.warning) {
|
|
253
|
+
console.log(chalk.yellow(`JIRA: ${result.warning}`));
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
console.log(chalk.yellow(`JIRA: jiraIssueKey "${changeMetadata.jiraIssueKey}" encontrado, mas transitions.done não está configurado em pastelsdd/jira.yaml. ` +
|
|
258
|
+
`Execute /pstld:jira-setup para configurar.`));
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
catch {
|
|
263
|
+
// JIRA transition is non-fatal — never block the archive
|
|
264
|
+
}
|
|
266
265
|
// Move change to archive (uses copy+remove on EPERM/EXDEV, e.g. Windows)
|
|
267
266
|
await moveDirectory(changeDir, archivePath);
|
|
268
267
|
console.log(`Change '${changeName}' archived as '${archiveName}'.`);
|
|
@@ -300,7 +299,7 @@ export class ArchiveCommand {
|
|
|
300
299
|
}
|
|
301
300
|
try {
|
|
302
301
|
const answer = await select({
|
|
303
|
-
message: 'Select a change to
|
|
302
|
+
message: 'Select a change to complete',
|
|
304
303
|
choices
|
|
305
304
|
});
|
|
306
305
|
return answer;
|
|
@@ -315,4 +314,4 @@ export class ArchiveCommand {
|
|
|
315
314
|
return new Date().toISOString().split('T')[0];
|
|
316
315
|
}
|
|
317
316
|
}
|
|
318
|
-
//# sourceMappingURL=
|
|
317
|
+
//# sourceMappingURL=complete.js.map
|
|
@@ -18,9 +18,9 @@ export const COMMAND_REGISTRY = [
|
|
|
18
18
|
},
|
|
19
19
|
{
|
|
20
20
|
name: 'profile',
|
|
21
|
-
description: 'Override global config profile (
|
|
21
|
+
description: 'Override global config profile (standard or dixi)',
|
|
22
22
|
takesValue: true,
|
|
23
|
-
values: ['
|
|
23
|
+
values: ['standard', 'dixi'],
|
|
24
24
|
},
|
|
25
25
|
],
|
|
26
26
|
},
|
|
@@ -128,8 +128,8 @@ export const COMMAND_REGISTRY = [
|
|
|
128
128
|
],
|
|
129
129
|
},
|
|
130
130
|
{
|
|
131
|
-
name: '
|
|
132
|
-
description: '
|
|
131
|
+
name: 'complete',
|
|
132
|
+
description: 'Complete a change and update main specs',
|
|
133
133
|
acceptsPositional: true,
|
|
134
134
|
positionalType: 'change-id',
|
|
135
135
|
positionals: [{ name: 'change-name', type: 'change-id', optional: true }],
|
|
@@ -139,10 +139,6 @@ export const COMMAND_REGISTRY = [
|
|
|
139
139
|
short: 'y',
|
|
140
140
|
description: 'Skip confirmation prompts',
|
|
141
141
|
},
|
|
142
|
-
{
|
|
143
|
-
name: 'skip-specs',
|
|
144
|
-
description: 'Skip spec update operations',
|
|
145
|
-
},
|
|
146
142
|
{
|
|
147
143
|
name: 'no-validate',
|
|
148
144
|
description: 'Skip validation (not recommended)',
|
|
@@ -847,7 +843,7 @@ export const COMMAND_REGISTRY = [
|
|
|
847
843
|
name: 'profile',
|
|
848
844
|
description: 'Configure workflow profile (interactive picker or preset shortcut)',
|
|
849
845
|
acceptsPositional: true,
|
|
850
|
-
positionals: [{ name: '
|
|
846
|
+
positionals: [{ name: 'name', optional: true }],
|
|
851
847
|
flags: [],
|
|
852
848
|
},
|
|
853
849
|
],
|
|
@@ -5,16 +5,12 @@ import { z } from 'zod';
|
|
|
5
5
|
*/
|
|
6
6
|
export declare const GlobalConfigSchema: z.ZodObject<{
|
|
7
7
|
featureFlags: z.ZodDefault<z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodBoolean>>>;
|
|
8
|
-
profile: z.ZodDefault<z.ZodOptional<z.
|
|
9
|
-
core: "core";
|
|
10
|
-
custom: "custom";
|
|
11
|
-
}>>>;
|
|
8
|
+
profile: z.ZodDefault<z.ZodOptional<z.ZodString>>;
|
|
12
9
|
delivery: z.ZodDefault<z.ZodOptional<z.ZodEnum<{
|
|
13
10
|
commands: "commands";
|
|
14
11
|
skills: "skills";
|
|
15
12
|
both: "both";
|
|
16
13
|
}>>>;
|
|
17
|
-
workflows: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
18
14
|
}, z.core.$loose>;
|
|
19
15
|
export type GlobalConfigType = z.infer<typeof GlobalConfigSchema>;
|
|
20
16
|
/**
|
|
@@ -10,16 +10,13 @@ export const GlobalConfigSchema = z
|
|
|
10
10
|
.optional()
|
|
11
11
|
.default({}),
|
|
12
12
|
profile: z
|
|
13
|
-
.
|
|
13
|
+
.string()
|
|
14
14
|
.optional()
|
|
15
15
|
.default('core'),
|
|
16
16
|
delivery: z
|
|
17
17
|
.enum(['both', 'skills', 'commands'])
|
|
18
18
|
.optional()
|
|
19
19
|
.default('both'),
|
|
20
|
-
workflows: z
|
|
21
|
-
.array(z.string())
|
|
22
|
-
.optional(),
|
|
23
20
|
})
|
|
24
21
|
.passthrough();
|
|
25
22
|
/**
|
|
@@ -30,7 +27,7 @@ export const DEFAULT_CONFIG = {
|
|
|
30
27
|
profile: 'core',
|
|
31
28
|
delivery: 'both',
|
|
32
29
|
};
|
|
33
|
-
const KNOWN_TOP_LEVEL_KEYS = new Set([...Object.keys(DEFAULT_CONFIG)
|
|
30
|
+
const KNOWN_TOP_LEVEL_KEYS = new Set([...Object.keys(DEFAULT_CONFIG)]);
|
|
34
31
|
/**
|
|
35
32
|
* Validate a config key path for CLI set operations.
|
|
36
33
|
* Unknown top-level keys are rejected unless explicitly allowed by the caller.
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
export declare const GLOBAL_CONFIG_DIR_NAME = "pscode";
|
|
2
2
|
export declare const GLOBAL_CONFIG_FILE_NAME = "config.json";
|
|
3
3
|
export declare const GLOBAL_DATA_DIR_NAME = "pscode";
|
|
4
|
-
export type Profile = 'core' | 'custom';
|
|
5
4
|
export type Delivery = 'both' | 'skills' | 'commands';
|
|
6
5
|
export interface GlobalConfig {
|
|
7
6
|
featureFlags?: Record<string, boolean>;
|
|
8
|
-
profile?:
|
|
7
|
+
profile?: string;
|
|
9
8
|
delivery?: Delivery;
|
|
10
|
-
workflows?: string[];
|
|
11
9
|
}
|
|
12
10
|
/**
|
|
13
11
|
* Gets the global configuration directory path following XDG Base Directory Specification.
|
package/dist/core/init.d.ts
CHANGED
|
@@ -32,6 +32,8 @@ export declare class InitCommand {
|
|
|
32
32
|
private generateSkillsAndCommands;
|
|
33
33
|
private createConfig;
|
|
34
34
|
private displaySuccessMessage;
|
|
35
|
+
private generateJiraFiles;
|
|
36
|
+
private handleDixiExtras;
|
|
35
37
|
private startSpinner;
|
|
36
38
|
private removeSkillDirs;
|
|
37
39
|
private removeCommandFiles;
|
package/dist/core/init.js
CHANGED
|
@@ -21,7 +21,9 @@ import { detectLegacyToolArtifacts, runLegacyToolMigration, formatLegacyToolDete
|
|
|
21
21
|
import { runTrelloInitPrompt } from './trello-init-prompt.js';
|
|
22
22
|
import { getToolsWithSkillsDir, getToolStates, getSkillTemplates, getCommandContents, generateSkillContent, } from './shared/index.js';
|
|
23
23
|
import { getGlobalConfig } from './global-config.js';
|
|
24
|
-
import { getProfileWorkflows, ALL_WORKFLOWS } from './profiles.js';
|
|
24
|
+
import { getProfileWorkflows, isValidProfile, DEFAULT_PROFILE, PROFILES, ALL_WORKFLOWS } from './profiles.js';
|
|
25
|
+
import { detectDixiStack, getDixiStackFamily, getDixiStackLabel, installDixiExtras } from './presets/dixi.js';
|
|
26
|
+
import { stringify as stringifyYaml } from 'yaml';
|
|
25
27
|
import { getAvailableTools } from './available-tools.js';
|
|
26
28
|
import { migrateIfNeeded } from './migration.js';
|
|
27
29
|
const require = createRequire(import.meta.url);
|
|
@@ -40,8 +42,7 @@ const WORKFLOW_TO_SKILL_DIR = {
|
|
|
40
42
|
'continue': 'pscode-continue-change',
|
|
41
43
|
'apply': 'pscode-apply-change',
|
|
42
44
|
'ff': 'pscode-ff-change',
|
|
43
|
-
'
|
|
44
|
-
'archive': 'pscode-archive-change',
|
|
45
|
+
'complete': 'pscode-archive-change',
|
|
45
46
|
'bulk-archive': 'pscode-bulk-archive-change',
|
|
46
47
|
'verify': 'pscode-verify-change',
|
|
47
48
|
'onboard': 'pscode-onboard',
|
|
@@ -49,6 +50,14 @@ const WORKFLOW_TO_SKILL_DIR = {
|
|
|
49
50
|
// Trello-specific workflows
|
|
50
51
|
'trello-setup': 'pscode-trello-setup',
|
|
51
52
|
'draft': 'pscode-trello-draft',
|
|
53
|
+
// Dixi-specific workflows
|
|
54
|
+
'rfc': 'pscode-dixi-rfc',
|
|
55
|
+
'design': 'pscode-dixi-design',
|
|
56
|
+
'tasks': 'pscode-dixi-tasks',
|
|
57
|
+
'arch-check': 'pscode-dixi-arch-check',
|
|
58
|
+
'adr': 'pscode-dixi-adr',
|
|
59
|
+
'jira-sync': 'pscode-dixi-jira-sync',
|
|
60
|
+
'dod': 'pscode-dixi-dod',
|
|
52
61
|
};
|
|
53
62
|
// -----------------------------------------------------------------------------
|
|
54
63
|
// Init Command Class
|
|
@@ -101,6 +110,13 @@ export class InitCommand {
|
|
|
101
110
|
const trelloConfigured = await this.handleTrelloSetup(pscodePath);
|
|
102
111
|
// Generate skills and commands for each tool
|
|
103
112
|
const results = await this.generateSkillsAndCommands(projectPath, validatedTools);
|
|
113
|
+
// Dixi profile extras: stack detection and .pscode-dixi.yaml
|
|
114
|
+
await this.handleDixiExtras(projectPath);
|
|
115
|
+
// Dixi profile: generate JIRA integration files
|
|
116
|
+
const resolvedProfile = this.resolveProfileOverride() ?? (isValidProfile(getGlobalConfig().profile ?? '') ? getGlobalConfig().profile : DEFAULT_PROFILE);
|
|
117
|
+
if (resolvedProfile === 'dixi') {
|
|
118
|
+
await this.generateJiraFiles(projectPath);
|
|
119
|
+
}
|
|
104
120
|
// Create config.yaml if needed
|
|
105
121
|
const configStatus = await this.createConfig(pscodePath, extendMode);
|
|
106
122
|
// Display success message
|
|
@@ -128,10 +144,11 @@ export class InitCommand {
|
|
|
128
144
|
if (this.profileOverride === undefined) {
|
|
129
145
|
return undefined;
|
|
130
146
|
}
|
|
131
|
-
if (this.profileOverride
|
|
147
|
+
if (isValidProfile(this.profileOverride)) {
|
|
132
148
|
return this.profileOverride;
|
|
133
149
|
}
|
|
134
|
-
|
|
150
|
+
const available = Object.keys(PROFILES).join(', ');
|
|
151
|
+
throw new Error(`Invalid profile "${this.profileOverride}". Available profiles: ${available}`);
|
|
135
152
|
}
|
|
136
153
|
// ═══════════════════════════════════════════════════════════
|
|
137
154
|
// LEGACY TOOL MIGRATION
|
|
@@ -431,14 +448,9 @@ export class InitCommand {
|
|
|
431
448
|
let removedSkillCount = 0;
|
|
432
449
|
// Read global config for profile and delivery settings (use --profile override if set)
|
|
433
450
|
const globalConfig = getGlobalConfig();
|
|
434
|
-
const profile = this.resolveProfileOverride() ?? globalConfig.profile ?? '
|
|
451
|
+
const profile = this.resolveProfileOverride() ?? (isValidProfile(globalConfig.profile ?? '') ? globalConfig.profile : DEFAULT_PROFILE);
|
|
435
452
|
const delivery = globalConfig.delivery ?? 'both';
|
|
436
|
-
const
|
|
437
|
-
// Trello workflows are always generated regardless of profile, so users can
|
|
438
|
-
// run /ps:trello-setup to configure the integration at any time.
|
|
439
|
-
const TRELLO_WORKFLOWS = ['trello-setup', 'task', 'draft'];
|
|
440
|
-
const workflowsSet = new Set([...profileWorkflows, ...TRELLO_WORKFLOWS]);
|
|
441
|
-
const workflows = [...workflowsSet];
|
|
453
|
+
const workflows = [...getProfileWorkflows(profile)];
|
|
442
454
|
// Get skill and command templates filtered by profile workflows
|
|
443
455
|
const shouldGenerateSkills = delivery !== 'commands';
|
|
444
456
|
const shouldGenerateCommands = delivery !== 'skills';
|
|
@@ -541,15 +553,13 @@ export class InitCommand {
|
|
|
541
553
|
if (results.refreshedTools.length > 0) {
|
|
542
554
|
console.log(`Refreshed: ${results.refreshedTools.map((t) => t.name).join(', ')}`);
|
|
543
555
|
}
|
|
544
|
-
// Show counts
|
|
556
|
+
// Show counts
|
|
545
557
|
const successfulTools = [...results.createdTools, ...results.refreshedTools];
|
|
546
558
|
if (successfulTools.length > 0) {
|
|
547
559
|
const globalConfig = getGlobalConfig();
|
|
548
|
-
const profile = this.
|
|
560
|
+
const profile = this.resolveProfileOverride() ?? (isValidProfile(globalConfig.profile ?? '') ? globalConfig.profile : DEFAULT_PROFILE);
|
|
549
561
|
const delivery = globalConfig.delivery ?? 'both';
|
|
550
|
-
const
|
|
551
|
-
const TRELLO_WORKFLOWS = ['trello-setup', 'task', 'draft'];
|
|
552
|
-
const workflows = [...new Set([...profileWorkflows, ...TRELLO_WORKFLOWS])];
|
|
562
|
+
const workflows = [...getProfileWorkflows(profile)];
|
|
553
563
|
const toolDirs = [...new Set(successfulTools.map((t) => t.skillsDir))].join(', ');
|
|
554
564
|
const skillCount = delivery !== 'commands' ? getSkillTemplates(workflows).length : 0;
|
|
555
565
|
const commandCount = delivery !== 'skills' ? getCommandContents(workflows).length : 0;
|
|
@@ -591,10 +601,10 @@ export class InitCommand {
|
|
|
591
601
|
else {
|
|
592
602
|
console.log(chalk.dim(`Config: skipped (non-interactive mode)`));
|
|
593
603
|
}
|
|
594
|
-
// Getting started
|
|
604
|
+
// Getting started
|
|
595
605
|
const globalCfg = getGlobalConfig();
|
|
596
|
-
const activeProfile = this.
|
|
597
|
-
const activeWorkflows = [...getProfileWorkflows(activeProfile
|
|
606
|
+
const activeProfile = this.resolveProfileOverride() ?? (isValidProfile(globalCfg.profile ?? '') ? globalCfg.profile : DEFAULT_PROFILE);
|
|
607
|
+
const activeWorkflows = [...getProfileWorkflows(activeProfile)];
|
|
598
608
|
console.log();
|
|
599
609
|
if (activeWorkflows.includes('propose')) {
|
|
600
610
|
console.log(chalk.bold('Getting started:'));
|
|
@@ -605,7 +615,7 @@ export class InitCommand {
|
|
|
605
615
|
console.log(' Start your first change: /ps:new "your idea"');
|
|
606
616
|
}
|
|
607
617
|
else {
|
|
608
|
-
console.log("Done. Run 'pscode config profile' to
|
|
618
|
+
console.log("Done. Run 'pscode config profile' to switch profiles.");
|
|
609
619
|
}
|
|
610
620
|
// Trello status
|
|
611
621
|
if (trelloConfigured) {
|
|
@@ -625,6 +635,56 @@ export class InitCommand {
|
|
|
625
635
|
}
|
|
626
636
|
console.log();
|
|
627
637
|
}
|
|
638
|
+
async generateJiraFiles(projectPath) {
|
|
639
|
+
const pastelsddPath = path.join(projectPath, 'pastelsdd');
|
|
640
|
+
await FileSystemUtils.createDirectory(pastelsddPath);
|
|
641
|
+
const jiraYamlPath = path.join(pastelsddPath, 'jira.yaml');
|
|
642
|
+
if (!fs.existsSync(jiraYamlPath)) {
|
|
643
|
+
const content = `project_key: ""\nboard_url: ""\nconfigured: false\ntransitions:\n done: ""\n`;
|
|
644
|
+
await FileSystemUtils.writeFile(jiraYamlPath, content);
|
|
645
|
+
}
|
|
646
|
+
const mcpJsonPath = path.join(projectPath, '.mcp.json');
|
|
647
|
+
let mcpConfig = {};
|
|
648
|
+
if (fs.existsSync(mcpJsonPath)) {
|
|
649
|
+
try {
|
|
650
|
+
const raw = fs.readFileSync(mcpJsonPath, 'utf-8');
|
|
651
|
+
mcpConfig = JSON.parse(raw);
|
|
652
|
+
}
|
|
653
|
+
catch {
|
|
654
|
+
console.log('Aviso: .mcp.json inválido — recriando com entrada Atlassian.');
|
|
655
|
+
mcpConfig = {};
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
const mcpServers = (mcpConfig.mcpServers ?? {});
|
|
659
|
+
if (!mcpServers['atlassian']) {
|
|
660
|
+
mcpServers['atlassian'] = {
|
|
661
|
+
command: 'npx',
|
|
662
|
+
args: ['-y', 'mcp-remote', 'https://mcp.atlassian.com/v1/sse'],
|
|
663
|
+
};
|
|
664
|
+
mcpConfig.mcpServers = mcpServers;
|
|
665
|
+
await FileSystemUtils.writeFile(mcpJsonPath, JSON.stringify(mcpConfig, null, 2) + '\n');
|
|
666
|
+
}
|
|
667
|
+
console.log('JIRA: edite pastelsdd/jira.yaml com project_key e board_url, depois use /pstld:jira-sync para testar a conexão.');
|
|
668
|
+
}
|
|
669
|
+
async handleDixiExtras(projectPath) {
|
|
670
|
+
const globalConfig = getGlobalConfig();
|
|
671
|
+
const profile = this.resolveProfileOverride() ?? (isValidProfile(globalConfig.profile ?? '') ? globalConfig.profile : DEFAULT_PROFILE);
|
|
672
|
+
if (profile !== 'dixi')
|
|
673
|
+
return;
|
|
674
|
+
const stack = detectDixiStack(projectPath);
|
|
675
|
+
const label = getDixiStackLabel(stack);
|
|
676
|
+
if (stack) {
|
|
677
|
+
console.log(`Dixi: stack detectada — ${label}`);
|
|
678
|
+
}
|
|
679
|
+
else {
|
|
680
|
+
console.log('Dixi: stack não detectada, usando configuração genérica');
|
|
681
|
+
}
|
|
682
|
+
installDixiExtras(projectPath, stack);
|
|
683
|
+
const family = getDixiStackFamily(stack);
|
|
684
|
+
const yamlContent = stringifyYaml({ stack, family, detectedAt: new Date().toISOString() });
|
|
685
|
+
const dixiYamlPath = path.join(projectPath, '.pscode-dixi.yaml');
|
|
686
|
+
await FileSystemUtils.writeFile(dixiYamlPath, yamlContent);
|
|
687
|
+
}
|
|
628
688
|
startSpinner(text) {
|
|
629
689
|
return ora({
|
|
630
690
|
text,
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
interface JiraConfig {
|
|
2
|
+
project_key?: string;
|
|
3
|
+
configured?: boolean;
|
|
4
|
+
transitions?: {
|
|
5
|
+
done?: string;
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
export interface JiraTransitionResult {
|
|
9
|
+
attempted: boolean;
|
|
10
|
+
success: boolean;
|
|
11
|
+
warning?: string;
|
|
12
|
+
}
|
|
13
|
+
export declare function readJiraConfig(projectDir: string): Promise<JiraConfig | null>;
|
|
14
|
+
export declare function tryTransitionJiraIssue(issueKey: string, transitionId: string): Promise<JiraTransitionResult>;
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=jira-transition.d.ts.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { parse as parseYaml } from 'yaml';
|
|
4
|
+
export async function readJiraConfig(projectDir) {
|
|
5
|
+
const jiraYamlPath = path.join(projectDir, 'pastelsdd', 'jira.yaml');
|
|
6
|
+
try {
|
|
7
|
+
const content = await fs.readFile(jiraYamlPath, 'utf-8');
|
|
8
|
+
return parseYaml(content);
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export async function tryTransitionJiraIssue(issueKey, transitionId) {
|
|
15
|
+
// Validate issueKey format
|
|
16
|
+
if (!/^[A-Z]+-[0-9]+$/.test(issueKey)) {
|
|
17
|
+
return {
|
|
18
|
+
attempted: false,
|
|
19
|
+
success: false,
|
|
20
|
+
warning: `jiraIssueKey "${issueKey}" não corresponde ao formato esperado [A-Z]+-[0-9]+. Transição JIRA ignorada.`,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
// The CLI cannot call the Atlassian MCP server directly.
|
|
24
|
+
// Log a message informing the user to transition manually or via /pstld:jira-sync.
|
|
25
|
+
console.log(`JIRA: issue ${issueKey} vinculada (transição "${transitionId}" pendente). ` +
|
|
26
|
+
`Use /pstld:jira-sync para confirmar o status ou transite manualmente.`);
|
|
27
|
+
return { attempted: true, success: true };
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=jira-transition.js.map
|
package/dist/core/migration.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Migration Utilities
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* Called by
|
|
4
|
+
* Scans installed workflow artifacts across tools.
|
|
5
|
+
* Called by init and update commands.
|
|
6
6
|
*/
|
|
7
7
|
import type { AIToolOption } from './config.js';
|
|
8
8
|
/**
|
|
@@ -10,14 +10,5 @@ import type { AIToolOption } from './config.js';
|
|
|
10
10
|
* the union of installed workflow IDs.
|
|
11
11
|
*/
|
|
12
12
|
export declare function scanInstalledWorkflows(projectPath: string, tools: AIToolOption[]): string[];
|
|
13
|
-
|
|
14
|
-
* Performs one-time migration if the global config does not yet have a profile field.
|
|
15
|
-
* Called by both init and update before profile resolution.
|
|
16
|
-
*
|
|
17
|
-
* - If no profile field exists and workflows are installed: sets profile to 'custom'
|
|
18
|
-
* with the detected workflows, preserving the user's existing setup.
|
|
19
|
-
* - If no profile field exists and no workflows are installed: no-op (defaults apply).
|
|
20
|
-
* - If profile field already exists: no-op.
|
|
21
|
-
*/
|
|
22
|
-
export declare function migrateIfNeeded(projectPath: string, tools: AIToolOption[]): void;
|
|
13
|
+
export declare function migrateIfNeeded(_projectPath: string, _tools: AIToolOption[]): void;
|
|
23
14
|
//# sourceMappingURL=migration.d.ts.map
|