@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
package/dist/core/migration.js
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
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
|
-
import { getGlobalConfig, getGlobalConfigPath, saveGlobalConfig } from './global-config.js';
|
|
8
7
|
import { CommandAdapterRegistry } from './command-generation/index.js';
|
|
9
8
|
import { WORKFLOW_TO_SKILL_DIR } from './profile-sync-drift.js';
|
|
10
9
|
import { ALL_WORKFLOWS } from './profiles.js';
|
|
11
10
|
import path from 'path';
|
|
12
11
|
import * as fs from 'fs';
|
|
13
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Scans installed workflow files across all detected tools and returns
|
|
14
|
+
* the union of installed workflow IDs.
|
|
15
|
+
*/
|
|
16
|
+
export function scanInstalledWorkflows(projectPath, tools) {
|
|
14
17
|
const installed = new Set();
|
|
15
|
-
let hasSkills = false;
|
|
16
|
-
let hasCommands = false;
|
|
17
18
|
for (const tool of tools) {
|
|
18
19
|
if (!tool.skillsDir)
|
|
19
20
|
continue;
|
|
@@ -23,7 +24,6 @@ function scanInstalledWorkflowArtifacts(projectPath, tools) {
|
|
|
23
24
|
const skillFile = path.join(skillsDir, skillDirName, 'SKILL.md');
|
|
24
25
|
if (fs.existsSync(skillFile)) {
|
|
25
26
|
installed.add(workflowId);
|
|
26
|
-
hasSkills = true;
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
const adapter = CommandAdapterRegistry.get(tool.value);
|
|
@@ -36,73 +36,11 @@ function scanInstalledWorkflowArtifacts(projectPath, tools) {
|
|
|
36
36
|
: path.join(projectPath, commandPath);
|
|
37
37
|
if (fs.existsSync(fullPath)) {
|
|
38
38
|
installed.add(workflowId);
|
|
39
|
-
hasCommands = true;
|
|
40
39
|
}
|
|
41
40
|
}
|
|
42
41
|
}
|
|
43
|
-
return
|
|
44
|
-
workflows: ALL_WORKFLOWS.filter((workflowId) => installed.has(workflowId)),
|
|
45
|
-
hasSkills,
|
|
46
|
-
hasCommands,
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
/**
|
|
50
|
-
* Scans installed workflow files across all detected tools and returns
|
|
51
|
-
* the union of installed workflow IDs.
|
|
52
|
-
*/
|
|
53
|
-
export function scanInstalledWorkflows(projectPath, tools) {
|
|
54
|
-
return scanInstalledWorkflowArtifacts(projectPath, tools).workflows;
|
|
55
|
-
}
|
|
56
|
-
function inferDelivery(artifacts) {
|
|
57
|
-
if (artifacts.hasSkills && artifacts.hasCommands) {
|
|
58
|
-
return 'both';
|
|
59
|
-
}
|
|
60
|
-
if (artifacts.hasCommands) {
|
|
61
|
-
return 'commands';
|
|
62
|
-
}
|
|
63
|
-
return 'skills';
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
|
-
* Performs one-time migration if the global config does not yet have a profile field.
|
|
67
|
-
* Called by both init and update before profile resolution.
|
|
68
|
-
*
|
|
69
|
-
* - If no profile field exists and workflows are installed: sets profile to 'custom'
|
|
70
|
-
* with the detected workflows, preserving the user's existing setup.
|
|
71
|
-
* - If no profile field exists and no workflows are installed: no-op (defaults apply).
|
|
72
|
-
* - If profile field already exists: no-op.
|
|
73
|
-
*/
|
|
74
|
-
export function migrateIfNeeded(projectPath, tools) {
|
|
75
|
-
const config = getGlobalConfig();
|
|
76
|
-
// Check raw config file for profile field presence
|
|
77
|
-
const configPath = getGlobalConfigPath();
|
|
78
|
-
let rawConfig = {};
|
|
79
|
-
try {
|
|
80
|
-
if (fs.existsSync(configPath)) {
|
|
81
|
-
rawConfig = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
catch {
|
|
85
|
-
return; // Can't read config, skip migration
|
|
86
|
-
}
|
|
87
|
-
// If profile is already explicitly set, no migration needed
|
|
88
|
-
if (rawConfig.profile !== undefined) {
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
// Scan for installed workflows
|
|
92
|
-
const artifacts = scanInstalledWorkflowArtifacts(projectPath, tools);
|
|
93
|
-
const installedWorkflows = artifacts.workflows;
|
|
94
|
-
if (installedWorkflows.length === 0) {
|
|
95
|
-
// No workflows installed, new user — defaults will apply
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
// Migrate: set profile to custom with detected workflows
|
|
99
|
-
config.profile = 'custom';
|
|
100
|
-
config.workflows = installedWorkflows;
|
|
101
|
-
if (rawConfig.delivery === undefined) {
|
|
102
|
-
config.delivery = inferDelivery(artifacts);
|
|
103
|
-
}
|
|
104
|
-
saveGlobalConfig(config);
|
|
105
|
-
console.log(`Migrated: custom profile with ${installedWorkflows.length} workflows`);
|
|
106
|
-
console.log("New in this version: /ps:propose. Try 'pscode config profile core' for the streamlined experience.");
|
|
42
|
+
return ALL_WORKFLOWS.filter((id) => installed.has(id));
|
|
107
43
|
}
|
|
44
|
+
// No-op kept for call-site compatibility during transition.
|
|
45
|
+
export function migrateIfNeeded(_projectPath, _tools) { }
|
|
108
46
|
//# sourceMappingURL=migration.js.map
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export type DixiStack = 'java-maven' | 'java-gradle' | 'next' | 'react' | 'node' | 'python';
|
|
2
|
+
export type DixiStackFamily = 'java' | 'react' | 'node' | 'python';
|
|
3
|
+
export declare function detectDixiStack(projectDir: string): DixiStack | null;
|
|
4
|
+
export declare function getDixiStackFamily(stack: DixiStack | null): DixiStackFamily | null;
|
|
5
|
+
export declare function getDixiStackLabel(stack: DixiStack | null): string;
|
|
6
|
+
/**
|
|
7
|
+
* Copies files recursively from sourceDir to targetDir.
|
|
8
|
+
* By default skips files that already exist (brownfield-safe).
|
|
9
|
+
* Files whose basename appears in options.overwrite are always overwritten.
|
|
10
|
+
*/
|
|
11
|
+
export declare function copyKitFiles(sourceDir: string, targetDir: string, options?: {
|
|
12
|
+
overwrite?: string[];
|
|
13
|
+
}): void;
|
|
14
|
+
/**
|
|
15
|
+
* Copies all files from srcDir into <destRoot>/pastelsdd/context/, skipping files that already exist.
|
|
16
|
+
* Creates the destination directory if needed.
|
|
17
|
+
*/
|
|
18
|
+
export declare function copyContextDocs(destRoot: string, srcDir: string): void;
|
|
19
|
+
export declare function installDixiClaudeMd(projectDir: string, family: DixiStackFamily | null): void;
|
|
20
|
+
export declare function detectBasePackage(projectRoot: string): string;
|
|
21
|
+
export declare function applyHexagonalSkeleton(projectRoot: string, basePackage: string): {
|
|
22
|
+
created: number;
|
|
23
|
+
skipped: number;
|
|
24
|
+
};
|
|
25
|
+
export declare function generateArchitectureTest(projectRoot: string, basePackage: string): boolean;
|
|
26
|
+
export declare function applyFeatureSlicedSkeleton(projectRoot: string): {
|
|
27
|
+
created: number;
|
|
28
|
+
skipped: number;
|
|
29
|
+
};
|
|
30
|
+
export declare function installEslintArchitectureTemplate(projectRoot: string): void;
|
|
31
|
+
export declare function installDixiExtras(projectDir: string, stack: DixiStack | null): void;
|
|
32
|
+
//# sourceMappingURL=dixi.d.ts.map
|
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import { parse as parseYaml } from 'yaml';
|
|
5
|
+
export function detectDixiStack(projectDir) {
|
|
6
|
+
if (fs.existsSync(path.join(projectDir, 'pom.xml')))
|
|
7
|
+
return 'java-maven';
|
|
8
|
+
if (fs.existsSync(path.join(projectDir, 'build.gradle')))
|
|
9
|
+
return 'java-gradle';
|
|
10
|
+
if (fs.existsSync(path.join(projectDir, 'next.config.js')) ||
|
|
11
|
+
fs.existsSync(path.join(projectDir, 'next.config.ts')) ||
|
|
12
|
+
fs.existsSync(path.join(projectDir, 'next.config.mjs')))
|
|
13
|
+
return 'next';
|
|
14
|
+
const pkgPath = path.join(projectDir, 'package.json');
|
|
15
|
+
if (fs.existsSync(pkgPath)) {
|
|
16
|
+
try {
|
|
17
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
18
|
+
const deps = { ...(pkg.dependencies ?? {}), ...(pkg.devDependencies ?? {}) };
|
|
19
|
+
if ('next' in deps)
|
|
20
|
+
return 'next';
|
|
21
|
+
if ('react' in deps)
|
|
22
|
+
return 'react';
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
// fallback to 'node' when package.json has invalid JSON
|
|
26
|
+
}
|
|
27
|
+
return 'node';
|
|
28
|
+
}
|
|
29
|
+
if (fs.existsSync(path.join(projectDir, 'pyproject.toml')))
|
|
30
|
+
return 'python';
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
export function getDixiStackFamily(stack) {
|
|
34
|
+
if (stack === null)
|
|
35
|
+
return null;
|
|
36
|
+
if (stack === 'java-maven' || stack === 'java-gradle')
|
|
37
|
+
return 'java';
|
|
38
|
+
if (stack === 'next')
|
|
39
|
+
return 'react';
|
|
40
|
+
return stack;
|
|
41
|
+
}
|
|
42
|
+
export function getDixiStackLabel(stack) {
|
|
43
|
+
if (stack === null)
|
|
44
|
+
return 'desconhecida';
|
|
45
|
+
const labels = {
|
|
46
|
+
'java-maven': 'Java/Maven',
|
|
47
|
+
'java-gradle': 'Java/Gradle',
|
|
48
|
+
'next': 'Next.js',
|
|
49
|
+
'react': 'React',
|
|
50
|
+
'node': 'Node.js',
|
|
51
|
+
'python': 'Python',
|
|
52
|
+
};
|
|
53
|
+
return labels[stack];
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Copies files recursively from sourceDir to targetDir.
|
|
57
|
+
* By default skips files that already exist (brownfield-safe).
|
|
58
|
+
* Files whose basename appears in options.overwrite are always overwritten.
|
|
59
|
+
*/
|
|
60
|
+
export function copyKitFiles(sourceDir, targetDir, options = {}) {
|
|
61
|
+
if (!fs.existsSync(sourceDir))
|
|
62
|
+
return;
|
|
63
|
+
const overwriteSet = new Set(options.overwrite ?? []);
|
|
64
|
+
function copyDir(src, dest) {
|
|
65
|
+
if (!fs.existsSync(dest)) {
|
|
66
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
67
|
+
}
|
|
68
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
69
|
+
for (const entry of entries) {
|
|
70
|
+
const srcPath = path.join(src, entry.name);
|
|
71
|
+
const destPath = path.join(dest, entry.name);
|
|
72
|
+
if (entry.isDirectory()) {
|
|
73
|
+
copyDir(srcPath, destPath);
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
if (fs.existsSync(destPath) && !overwriteSet.has(entry.name)) {
|
|
77
|
+
console.log(` ${entry.name} já existe — pulado`);
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
const destDirPath = path.dirname(destPath);
|
|
81
|
+
if (!fs.existsSync(destDirPath)) {
|
|
82
|
+
fs.mkdirSync(destDirPath, { recursive: true });
|
|
83
|
+
}
|
|
84
|
+
fs.copyFileSync(srcPath, destPath);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
copyDir(sourceDir, targetDir);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Copies all files from srcDir into <destRoot>/pastelsdd/context/, skipping files that already exist.
|
|
92
|
+
* Creates the destination directory if needed.
|
|
93
|
+
*/
|
|
94
|
+
export function copyContextDocs(destRoot, srcDir) {
|
|
95
|
+
if (!fs.existsSync(srcDir))
|
|
96
|
+
return;
|
|
97
|
+
const contextDir = path.join(destRoot, 'pastelsdd', 'context');
|
|
98
|
+
if (!fs.existsSync(contextDir)) {
|
|
99
|
+
fs.mkdirSync(contextDir, { recursive: true });
|
|
100
|
+
}
|
|
101
|
+
const files = fs.readdirSync(srcDir);
|
|
102
|
+
for (const file of files) {
|
|
103
|
+
const src = path.join(srcDir, file);
|
|
104
|
+
const dest = path.join(contextDir, file);
|
|
105
|
+
if (fs.existsSync(dest)) {
|
|
106
|
+
console.log(` ${file} já existe — pulado`);
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
fs.copyFileSync(src, dest);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
export function installDixiClaudeMd(projectDir, family) {
|
|
113
|
+
const currentFile = fileURLToPath(import.meta.url);
|
|
114
|
+
const packageRoot = path.join(path.dirname(currentFile), '..', '..', '..');
|
|
115
|
+
const claudeRuntimeDir = path.join(packageRoot, 'pscode', 'content', 'dixi', 'claude-runtime');
|
|
116
|
+
let templateName;
|
|
117
|
+
if (family === 'java') {
|
|
118
|
+
templateName = 'CLAUDE.md.java.template';
|
|
119
|
+
}
|
|
120
|
+
else if (family === 'react') {
|
|
121
|
+
templateName = 'CLAUDE.md.react.template';
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
templateName = 'CLAUDE.md.java.template';
|
|
125
|
+
console.log('Dixi: stack não detectada, instalando CLAUDE.md genérico (baseado em Java). ' +
|
|
126
|
+
'Edite .pscode-dixi.yaml para corrigir.');
|
|
127
|
+
}
|
|
128
|
+
const templatePath = path.join(claudeRuntimeDir, templateName);
|
|
129
|
+
const templateContent = fs.readFileSync(templatePath, 'utf-8');
|
|
130
|
+
const claudeMdPath = path.join(projectDir, 'CLAUDE.md');
|
|
131
|
+
const marker = '<!-- dixi-constitutional -->';
|
|
132
|
+
if (fs.existsSync(claudeMdPath)) {
|
|
133
|
+
const existing = fs.readFileSync(claudeMdPath, 'utf-8');
|
|
134
|
+
if (existing.includes(marker)) {
|
|
135
|
+
console.log('Dixi: CLAUDE.md já contém seção constitucional — pulando.');
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
fs.writeFileSync(claudeMdPath, existing + '\n' + templateContent, { encoding: 'utf-8' });
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
fs.writeFileSync(claudeMdPath, templateContent, { encoding: 'utf-8' });
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
const PSTLD_COMMANDS_SRC = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', '..', '..', 'pscode', 'content', 'dixi', 'claude-runtime', 'commands');
|
|
145
|
+
const PSTLD_SKILLS_SRC = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', '..', '..', 'pscode', 'content', 'dixi', 'claude-runtime', 'skills');
|
|
146
|
+
const HOOKS_SRC = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', '..', '..', 'pscode', 'content', 'dixi', 'claude-runtime', 'hooks');
|
|
147
|
+
const DIXI_HOOK_ENTRIES = [
|
|
148
|
+
{ event: 'PreToolUse', matcher: 'Edit|Write', command: 'node .claude/hooks/arch-guard.mjs' },
|
|
149
|
+
{ event: 'UserPromptSubmit', matcher: null, command: 'node .claude/hooks/jira-context.mjs' },
|
|
150
|
+
];
|
|
151
|
+
function mergeSettingsHooks(settingsPath) {
|
|
152
|
+
let settings = {};
|
|
153
|
+
if (fs.existsSync(settingsPath)) {
|
|
154
|
+
try {
|
|
155
|
+
settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
|
|
156
|
+
}
|
|
157
|
+
catch (e) {
|
|
158
|
+
console.log(`Dixi: settings.json inválido — criando novo arquivo. (${e instanceof Error ? e.message : String(e)})`);
|
|
159
|
+
settings = {};
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (!settings.hooks || typeof settings.hooks !== 'object' || Array.isArray(settings.hooks)) {
|
|
163
|
+
settings.hooks = {};
|
|
164
|
+
}
|
|
165
|
+
const hooks = settings.hooks;
|
|
166
|
+
for (const { event, matcher, command } of DIXI_HOOK_ENTRIES) {
|
|
167
|
+
if (!Array.isArray(hooks[event])) {
|
|
168
|
+
hooks[event] = [];
|
|
169
|
+
}
|
|
170
|
+
const eventHooks = hooks[event];
|
|
171
|
+
const alreadyExists = eventHooks.some(entry => Array.isArray(entry.hooks) && entry.hooks.some(h => h.command === command));
|
|
172
|
+
if (!alreadyExists) {
|
|
173
|
+
const entry = {
|
|
174
|
+
hooks: [{ type: 'command', command }],
|
|
175
|
+
};
|
|
176
|
+
if (matcher)
|
|
177
|
+
entry.matcher = matcher;
|
|
178
|
+
eventHooks.push(entry);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), { encoding: 'utf-8' });
|
|
182
|
+
}
|
|
183
|
+
const ARCHITECTURES_BASE = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', '..', '..', 'pscode', 'content', 'dixi', 'architectures');
|
|
184
|
+
// Task 3.2
|
|
185
|
+
export function detectBasePackage(projectRoot) {
|
|
186
|
+
const pomPath = path.join(projectRoot, 'pom.xml');
|
|
187
|
+
if (!fs.existsSync(pomPath)) {
|
|
188
|
+
console.log('[dixi] detectBasePackage: pom.xml não encontrado — usando fallback com.example.app. Ajuste manualmente o basePackage no ArchitectureTest.java gerado.');
|
|
189
|
+
return 'com.example.app';
|
|
190
|
+
}
|
|
191
|
+
const pomContent = fs.readFileSync(pomPath, 'utf-8');
|
|
192
|
+
const groupIdMatch = pomContent.match(/<groupId>([^<]+)<\/groupId>/);
|
|
193
|
+
const artifactIdMatch = pomContent.match(/<artifactId>([^<]+)<\/artifactId>/);
|
|
194
|
+
if (!groupIdMatch || !artifactIdMatch) {
|
|
195
|
+
console.log('[dixi] detectBasePackage: groupId ou artifactId não encontrado no pom.xml — usando fallback com.example.app.');
|
|
196
|
+
return 'com.example.app';
|
|
197
|
+
}
|
|
198
|
+
const groupId = groupIdMatch[1].trim();
|
|
199
|
+
const artifactId = artifactIdMatch[1].trim().replace(/-/g, '');
|
|
200
|
+
return `${groupId}.${artifactId}`;
|
|
201
|
+
}
|
|
202
|
+
// Task 3.1
|
|
203
|
+
export function applyHexagonalSkeleton(projectRoot, basePackage) {
|
|
204
|
+
const skeletonPath = path.join(ARCHITECTURES_BASE, 'hexagonal-spring', 'skeleton.yaml');
|
|
205
|
+
if (!fs.existsSync(skeletonPath))
|
|
206
|
+
return { created: 0, skipped: 0 };
|
|
207
|
+
const skeletonData = parseYaml(fs.readFileSync(skeletonPath, 'utf-8'));
|
|
208
|
+
const basePackageDir = basePackage.replace(/\./g, '/');
|
|
209
|
+
let created = 0;
|
|
210
|
+
let skipped = 0;
|
|
211
|
+
for (const dir of skeletonData.dirs) {
|
|
212
|
+
const resolvedDir = dir.replace('{basePackageDir}', basePackageDir);
|
|
213
|
+
const dirPath = path.join(projectRoot, resolvedDir);
|
|
214
|
+
const gitkeepPath = path.join(dirPath, '.gitkeep');
|
|
215
|
+
if (!fs.existsSync(dirPath)) {
|
|
216
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
217
|
+
}
|
|
218
|
+
if (!fs.existsSync(gitkeepPath)) {
|
|
219
|
+
fs.writeFileSync(gitkeepPath, '');
|
|
220
|
+
created++;
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
skipped++;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return { created, skipped };
|
|
227
|
+
}
|
|
228
|
+
// Task 3.3
|
|
229
|
+
export function generateArchitectureTest(projectRoot, basePackage) {
|
|
230
|
+
const templatePath = path.join(ARCHITECTURES_BASE, 'hexagonal-spring', 'ArchitectureTest.java.template');
|
|
231
|
+
if (!fs.existsSync(templatePath))
|
|
232
|
+
return false;
|
|
233
|
+
const basePackageDir = basePackage.replace(/\./g, '/');
|
|
234
|
+
const destDir = path.join(projectRoot, 'src', 'test', 'java', basePackageDir);
|
|
235
|
+
const destPath = path.join(destDir, 'ArchitectureTest.java');
|
|
236
|
+
if (fs.existsSync(destPath)) {
|
|
237
|
+
console.log('[dixi] ArchitectureTest.java: arquivo existente — ignorado');
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
const template = fs.readFileSync(templatePath, 'utf-8');
|
|
241
|
+
const content = template.replace(/\{basePackage\}/g, basePackage);
|
|
242
|
+
if (!fs.existsSync(destDir)) {
|
|
243
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
244
|
+
}
|
|
245
|
+
fs.writeFileSync(destPath, content, { encoding: 'utf-8' });
|
|
246
|
+
return true;
|
|
247
|
+
}
|
|
248
|
+
// Task 3.4
|
|
249
|
+
export function applyFeatureSlicedSkeleton(projectRoot) {
|
|
250
|
+
const skeletonPath = path.join(ARCHITECTURES_BASE, 'feature-sliced-react', 'skeleton.yaml');
|
|
251
|
+
if (!fs.existsSync(skeletonPath))
|
|
252
|
+
return { created: 0, skipped: 0 };
|
|
253
|
+
const skeletonData = parseYaml(fs.readFileSync(skeletonPath, 'utf-8'));
|
|
254
|
+
const readmeTemplatePath = path.join(ARCHITECTURES_BASE, 'feature-sliced-react', 'features', 'README.md.template');
|
|
255
|
+
let created = 0;
|
|
256
|
+
let skipped = 0;
|
|
257
|
+
for (const dir of skeletonData.dirs) {
|
|
258
|
+
const dirPath = path.join(projectRoot, dir);
|
|
259
|
+
if (!fs.existsSync(dirPath)) {
|
|
260
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
261
|
+
}
|
|
262
|
+
if (dir.endsWith('features')) {
|
|
263
|
+
const readmePath = path.join(dirPath, 'README.md');
|
|
264
|
+
if (!fs.existsSync(readmePath) && fs.existsSync(readmeTemplatePath)) {
|
|
265
|
+
fs.copyFileSync(readmeTemplatePath, readmePath);
|
|
266
|
+
created++;
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
skipped++;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
const gitkeepPath = path.join(dirPath, '.gitkeep');
|
|
274
|
+
if (!fs.existsSync(gitkeepPath)) {
|
|
275
|
+
fs.writeFileSync(gitkeepPath, '');
|
|
276
|
+
created++;
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
skipped++;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return { created, skipped };
|
|
284
|
+
}
|
|
285
|
+
// Task 3.5
|
|
286
|
+
export function installEslintArchitectureTemplate(projectRoot) {
|
|
287
|
+
const templatePath = path.join(ARCHITECTURES_BASE, 'feature-sliced-react', 'eslint-architecture.mjs.template');
|
|
288
|
+
if (!fs.existsSync(templatePath))
|
|
289
|
+
return;
|
|
290
|
+
const destPath = path.join(projectRoot, 'eslint-architecture.mjs');
|
|
291
|
+
if (!fs.existsSync(destPath)) {
|
|
292
|
+
fs.copyFileSync(templatePath, destPath);
|
|
293
|
+
}
|
|
294
|
+
console.log('\n[dixi] eslint-architecture.mjs instalado.\n' +
|
|
295
|
+
' Para ativar as regras arquiteturais, adicione ao seu eslint.config.js:\n\n' +
|
|
296
|
+
' import architectureRules from \'./eslint-architecture.mjs\';\n' +
|
|
297
|
+
' export default [...existingConfig, ...architectureRules];\n');
|
|
298
|
+
}
|
|
299
|
+
export function installDixiExtras(projectDir, stack) {
|
|
300
|
+
const family = getDixiStackFamily(stack);
|
|
301
|
+
// Resolve package content root: dist/core/presets/ → package root → pscode/content/dixi/
|
|
302
|
+
const currentFile = fileURLToPath(import.meta.url);
|
|
303
|
+
const packageRoot = path.join(path.dirname(currentFile), '..', '..', '..');
|
|
304
|
+
const contentBase = path.join(packageRoot, 'pscode', 'content', 'dixi', 'context');
|
|
305
|
+
const kitBase = path.join(packageRoot, 'pscode', 'content', 'dixi', 'kit');
|
|
306
|
+
// Task 4.5: Ensure pastelsdd/context/ exists in the client repo
|
|
307
|
+
const contextDir = path.join(projectDir, 'pastelsdd', 'context');
|
|
308
|
+
if (!fs.existsSync(contextDir)) {
|
|
309
|
+
fs.mkdirSync(contextDir, { recursive: true });
|
|
310
|
+
}
|
|
311
|
+
// Task 4.2: Always copy shared/ docs
|
|
312
|
+
copyContextDocs(projectDir, path.join(contentBase, 'shared'));
|
|
313
|
+
if (family === null || family === 'node') {
|
|
314
|
+
console.log('Dixi: Stack não detectada — apenas docs compartilhados instalados. ' +
|
|
315
|
+
'Configure `family` em `.pscode-dixi.yaml` para instalar docs específicos de stack.');
|
|
316
|
+
}
|
|
317
|
+
// Task 4.3: Copy java/ only for Java projects
|
|
318
|
+
if (family === 'java') {
|
|
319
|
+
copyContextDocs(projectDir, path.join(contentBase, 'java'));
|
|
320
|
+
}
|
|
321
|
+
// Task 4.4: Copy react/ only for React projects
|
|
322
|
+
if (family === 'react') {
|
|
323
|
+
copyContextDocs(projectDir, path.join(contentBase, 'react'));
|
|
324
|
+
}
|
|
325
|
+
// Task 4.2: Copy shared SDLC kit (always) — PR template always overwritten
|
|
326
|
+
copyKitFiles(path.join(kitBase, 'shared'), projectDir, {
|
|
327
|
+
overwrite: ['pull_request_template.md'],
|
|
328
|
+
});
|
|
329
|
+
// Task 4.3: Copy java SDLC kit
|
|
330
|
+
if (family === 'java') {
|
|
331
|
+
copyKitFiles(path.join(kitBase, 'java'), projectDir);
|
|
332
|
+
console.log('\nDixi [Java kit instalado]:\n' +
|
|
333
|
+
' Adicione ao pom.xml:\n' +
|
|
334
|
+
' • plugin commitlint: npm install --save-dev @commitlint/cli @commitlint/config-conventional commitlint-plugin-jira-rules\n' +
|
|
335
|
+
' • plugin Jacoco: <groupId>org.jacoco</groupId><artifactId>jacoco-maven-plugin</artifactId>\n' +
|
|
336
|
+
' • plugin Husky (se gerenciado via frontend): npx husky install\n' +
|
|
337
|
+
' Atenção: o job archunit requer ArchitectureTest.java (gerado pelo pscode init skeleton).');
|
|
338
|
+
}
|
|
339
|
+
// Task 4.4: Copy react SDLC kit
|
|
340
|
+
if (family === 'react') {
|
|
341
|
+
copyKitFiles(path.join(kitBase, 'react'), projectDir);
|
|
342
|
+
console.log('\nDixi [React kit instalado]:\n' +
|
|
343
|
+
' Execute:\n' +
|
|
344
|
+
' npm install --save-dev @commitlint/cli @commitlint/config-conventional commitlint-plugin-jira-rules husky lint-staged prettier eslint\n' +
|
|
345
|
+
' npx husky install\n' +
|
|
346
|
+
' Adicione ao package.json: "prepare": "husky install"');
|
|
347
|
+
}
|
|
348
|
+
installDixiClaudeMd(projectDir, family);
|
|
349
|
+
// Task 3.6 + 3.7: Apply architectural skeleton by stack
|
|
350
|
+
if (family === 'java') {
|
|
351
|
+
const basePackage = detectBasePackage(projectDir);
|
|
352
|
+
const skeletonResult = applyHexagonalSkeleton(projectDir, basePackage);
|
|
353
|
+
console.log(`[dixi] skeleton hexagonal: ${skeletonResult.created} diretórios criados, ${skeletonResult.skipped} ignorados`);
|
|
354
|
+
const archTestCreated = generateArchitectureTest(projectDir, basePackage);
|
|
355
|
+
console.log(`[dixi] ArchitectureTest.java: ${archTestCreated ? 'criado' : 'ignorado (já existe)'}`);
|
|
356
|
+
}
|
|
357
|
+
else if (family === 'react') {
|
|
358
|
+
const skeletonResult = applyFeatureSlicedSkeleton(projectDir);
|
|
359
|
+
console.log(`[dixi] skeleton feature-sliced: ${skeletonResult.created} arquivos criados, ${skeletonResult.skipped} ignorados`);
|
|
360
|
+
installEslintArchitectureTemplate(projectDir);
|
|
361
|
+
}
|
|
362
|
+
else {
|
|
363
|
+
console.log('[dixi] skeleton arquitetural: stack não reconhecida — skeleton não disponível para esta stack');
|
|
364
|
+
}
|
|
365
|
+
// Copy /pstld:* slash commands to .claude/commands/pstld/ (idempotent — overwrites)
|
|
366
|
+
const pstldCommandsDir = path.join(projectDir, '.claude', 'commands', 'pstld');
|
|
367
|
+
if (!fs.existsSync(pstldCommandsDir)) {
|
|
368
|
+
fs.mkdirSync(pstldCommandsDir, { recursive: true });
|
|
369
|
+
}
|
|
370
|
+
if (fs.existsSync(PSTLD_COMMANDS_SRC)) {
|
|
371
|
+
const files = fs.readdirSync(PSTLD_COMMANDS_SRC);
|
|
372
|
+
for (const file of files) {
|
|
373
|
+
fs.copyFileSync(path.join(PSTLD_COMMANDS_SRC, file), path.join(pstldCommandsDir, file));
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
// Copy pstld-* skills to .claude/skills/<name>/SKILL.md (idempotent — overwrites)
|
|
377
|
+
if (fs.existsSync(PSTLD_SKILLS_SRC)) {
|
|
378
|
+
const skillFiles = fs.readdirSync(PSTLD_SKILLS_SRC).filter(f => f.endsWith('.md'));
|
|
379
|
+
for (const file of skillFiles) {
|
|
380
|
+
const skillName = file.replace(/\.md$/, '');
|
|
381
|
+
const skillDir = path.join(projectDir, '.claude', 'skills', skillName);
|
|
382
|
+
if (!fs.existsSync(skillDir)) {
|
|
383
|
+
fs.mkdirSync(skillDir, { recursive: true });
|
|
384
|
+
}
|
|
385
|
+
fs.copyFileSync(path.join(PSTLD_SKILLS_SRC, file), path.join(skillDir, 'SKILL.md'));
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
// Copy hooks to .claude/hooks/ (brownfield-safe: skip if file already exists)
|
|
389
|
+
const hooksDestDir = path.join(projectDir, '.claude', 'hooks');
|
|
390
|
+
if (!fs.existsSync(hooksDestDir)) {
|
|
391
|
+
fs.mkdirSync(hooksDestDir, { recursive: true });
|
|
392
|
+
}
|
|
393
|
+
if (fs.existsSync(HOOKS_SRC)) {
|
|
394
|
+
const hookFiles = fs.readdirSync(HOOKS_SRC).filter(f => f.endsWith('.mjs'));
|
|
395
|
+
for (const file of hookFiles) {
|
|
396
|
+
const dest = path.join(hooksDestDir, file);
|
|
397
|
+
if (!fs.existsSync(dest)) {
|
|
398
|
+
fs.copyFileSync(path.join(HOOKS_SRC, file), dest);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
// Merge .claude/settings.json with hook registrations (never overwrite existing config)
|
|
403
|
+
mergeSettingsHooks(path.join(projectDir, '.claude', 'settings.json'));
|
|
404
|
+
}
|
|
405
|
+
//# sourceMappingURL=dixi.js.map
|
|
@@ -13,8 +13,7 @@ export const WORKFLOW_TO_SKILL_DIR = {
|
|
|
13
13
|
'continue': 'pscode-continue-change',
|
|
14
14
|
'apply': 'pscode-apply-change',
|
|
15
15
|
'ff': 'pscode-ff-change',
|
|
16
|
-
'
|
|
17
|
-
'archive': 'pscode-archive-change',
|
|
16
|
+
'complete': 'pscode-archive-change',
|
|
18
17
|
'bulk-archive': 'pscode-bulk-archive-change',
|
|
19
18
|
'verify': 'pscode-verify-change',
|
|
20
19
|
'onboard': 'pscode-onboard',
|
|
@@ -22,6 +21,14 @@ export const WORKFLOW_TO_SKILL_DIR = {
|
|
|
22
21
|
// Trello-specific workflows
|
|
23
22
|
'trello-setup': 'pscode-trello-setup',
|
|
24
23
|
'draft': 'pscode-trello-draft',
|
|
24
|
+
// Dixi-specific workflows
|
|
25
|
+
'rfc': 'pscode-dixi-rfc',
|
|
26
|
+
'design': 'pscode-dixi-design',
|
|
27
|
+
'tasks': 'pscode-dixi-tasks',
|
|
28
|
+
'arch-check': 'pscode-dixi-arch-check',
|
|
29
|
+
'adr': 'pscode-dixi-adr',
|
|
30
|
+
'jira-sync': 'pscode-dixi-jira-sync',
|
|
31
|
+
'dod': 'pscode-dixi-dod',
|
|
25
32
|
};
|
|
26
33
|
function toKnownWorkflows(workflows) {
|
|
27
34
|
return workflows.filter((workflow) => ALL_WORKFLOWS.includes(workflow));
|
package/dist/core/profiles.d.ts
CHANGED
|
@@ -1,26 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Predefined Workflow Profiles
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Add, remove or edit profiles here. Users select a profile via
|
|
5
|
+
* `pscode init --profile <name>` or `pscode config profile <name>`.
|
|
6
|
+
* The workflow lists are fixed in code — users cannot customise them.
|
|
6
7
|
*/
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Core workflows included in the 'core' profile.
|
|
10
|
-
* These provide the streamlined experience for new users.
|
|
11
|
-
*/
|
|
12
|
-
export declare const CORE_WORKFLOWS: readonly ["propose", "explore", "apply", "sync", "archive"];
|
|
13
|
-
/**
|
|
14
|
-
* All available workflows in the system.
|
|
15
|
-
*/
|
|
16
|
-
export declare const ALL_WORKFLOWS: readonly ["propose", "explore", "new", "continue", "apply", "ff", "sync", "archive", "bulk-archive", "verify", "onboard", "trello-setup", "draft"];
|
|
8
|
+
export declare const ALL_WORKFLOWS: readonly ["propose", "explore", "new", "continue", "apply", "ff", "complete", "bulk-archive", "verify", "onboard", "trello-setup", "draft", "rfc", "design", "tasks", "arch-check", "adr", "jira-sync", "dod"];
|
|
17
9
|
export type WorkflowId = (typeof ALL_WORKFLOWS)[number];
|
|
18
|
-
export
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
10
|
+
export interface ProfileDefinition {
|
|
11
|
+
description: string;
|
|
12
|
+
workflows: readonly WorkflowId[];
|
|
13
|
+
}
|
|
14
|
+
export declare const PROFILES: {
|
|
15
|
+
readonly standard: {
|
|
16
|
+
readonly description: "Padrão — propose, explore, apply, complete";
|
|
17
|
+
readonly workflows: readonly ["propose", "explore", "apply", "complete"];
|
|
18
|
+
};
|
|
19
|
+
readonly dixi: {
|
|
20
|
+
readonly description: "Dixi — RFC→Design→Tasks→Apply com guardrails para Java/Spring e React/Next.js";
|
|
21
|
+
readonly workflows: readonly ["rfc", "design", "tasks", "apply", "arch-check", "adr", "jira-sync", "dod"];
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
export type ProfileName = keyof typeof PROFILES;
|
|
25
|
+
export declare const DEFAULT_PROFILE: ProfileName;
|
|
26
|
+
export declare function getProfileWorkflows(profile: ProfileName): readonly WorkflowId[];
|
|
27
|
+
export declare function isValidProfile(name: string): name is ProfileName;
|
|
26
28
|
//# sourceMappingURL=profiles.d.ts.map
|