@polymorphism-tech/morph-spec 4.8.19 → 4.10.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/CLAUDE.md +21 -0
- package/README.md +2 -2
- package/bin/morph-spec.js +44 -55
- package/bin/task-manager.js +133 -20
- package/bin/validate.js +67 -33
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +201 -203
- package/docs/QUICKSTART.md +2 -2
- package/framework/CLAUDE.md +99 -77
- package/framework/agents.json +734 -182
- package/framework/commands/commit.md +166 -0
- package/framework/commands/morph-apply.md +13 -2
- package/framework/commands/morph-archive.md +8 -2
- package/framework/commands/morph-infra.md +6 -0
- package/framework/commands/morph-preflight.md +6 -0
- package/framework/commands/morph-proposal.md +56 -7
- package/framework/commands/morph-status.md +6 -0
- package/framework/commands/morph-troubleshoot.md +6 -0
- package/framework/hooks/claude-code/notification/approval-reminder.js +3 -2
- package/framework/hooks/claude-code/post-tool-use/context-refresh.js +1 -1
- package/framework/hooks/claude-code/post-tool-use/dispatch.js +155 -32
- package/framework/hooks/claude-code/post-tool-use/skill-reminder.js +78 -0
- package/framework/hooks/claude-code/post-tool-use/validator-feedback.js +8 -17
- package/framework/hooks/claude-code/pre-compact/save-morph-context.js +16 -3
- package/framework/hooks/claude-code/pre-tool-use/enforce-phase-writes.js +4 -3
- package/framework/hooks/claude-code/pre-tool-use/protect-spec-files.js +4 -3
- package/framework/hooks/claude-code/pre-tool-use/task-tracking-guard.js +60 -0
- package/framework/hooks/claude-code/session-start/inject-morph-context.js +124 -2
- package/framework/hooks/claude-code/session-start/post-compact-restore.js +41 -0
- package/framework/hooks/claude-code/statusline.py +76 -30
- package/framework/hooks/claude-code/stop/validate-completion.js +2 -15
- package/framework/hooks/claude-code/user-prompt/enrich-prompt.js +23 -5
- package/framework/hooks/claude-code/user-prompt/set-terminal-title.js +14 -6
- package/framework/hooks/shared/activity-logger.js +0 -24
- package/framework/hooks/shared/compact-restore.js +100 -0
- package/framework/hooks/shared/dispatch-helpers.js +116 -0
- package/framework/hooks/shared/phase-utils.js +12 -5
- package/framework/hooks/shared/skill-reminder-helpers.js +79 -0
- package/framework/hooks/shared/stale-task-reset.js +57 -0
- package/framework/hooks/shared/state-reader.js +29 -5
- package/framework/hooks/shared/worktree-helpers.js +53 -0
- package/framework/phases.json +69 -14
- package/framework/rules/morph-workflow.md +88 -86
- package/framework/skills/level-0-meta/mcp-registry.json +86 -51
- package/framework/skills/level-0-meta/{brainstorming → morph-brainstorming}/SKILL.md +14 -17
- package/framework/skills/level-0-meta/morph-checklist/SKILL.md +2 -2
- package/framework/skills/level-0-meta/{code-review → morph-code-review}/SKILL.md +2 -2
- package/framework/skills/level-0-meta/{code-review-nextjs → morph-code-review-nextjs}/SKILL.md +163 -163
- package/framework/skills/level-0-meta/{frontend-review → morph-frontend-review}/SKILL.md +9 -9
- package/framework/skills/level-0-meta/morph-init/SKILL.md +77 -12
- package/framework/skills/level-0-meta/{post-implementation → morph-post-implementation}/SKILL.md +62 -15
- package/framework/skills/level-0-meta/morph-replicate/SKILL.md +5 -5
- package/framework/skills/level-0-meta/morph-replicate/references/blazor-html-mapping.md +1 -1
- package/framework/skills/level-0-meta/{simulation-checklist → morph-simulation-checklist}/SKILL.md +1 -1
- package/framework/skills/level-0-meta/{terminal-title → morph-terminal-title}/SKILL.md +2 -2
- package/framework/skills/level-0-meta/{tool-usage-guide → morph-tool-usage-guide}/SKILL.md +3 -4
- package/framework/skills/level-0-meta/{tool-usage-guide → morph-tool-usage-guide}/references/tools-per-phase.md +7 -7
- package/framework/skills/level-0-meta/{verification-before-completion → morph-verification-before-completion}/SKILL.md +2 -2
- package/framework/skills/level-0-meta/{verification-before-completion → morph-verification-before-completion}/scripts/check-phase-outputs.mjs +2 -2
- package/framework/skills/level-1-workflows/morph-phase-clarify/SKILL.md +238 -0
- package/framework/skills/level-1-workflows/{phase-codebase-analysis → morph-phase-codebase-analysis}/SKILL.md +3 -3
- package/framework/skills/level-1-workflows/morph-phase-design/SKILL.md +507 -0
- package/framework/skills/level-1-workflows/{phase-implement → morph-phase-implement}/SKILL.md +168 -27
- package/framework/skills/level-1-workflows/morph-phase-implement/prompts/code-quality-reviewer-prompt.md +50 -0
- package/framework/skills/level-1-workflows/morph-phase-implement/prompts/implementer-prompt.md +45 -0
- package/framework/skills/level-1-workflows/morph-phase-implement/prompts/spec-reviewer-prompt.md +47 -0
- package/framework/skills/level-1-workflows/morph-phase-plan/SKILL.md +254 -0
- package/framework/skills/level-1-workflows/{phase-setup → morph-phase-setup}/SKILL.md +50 -3
- package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/SKILL.md +48 -11
- package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/scripts/validate-tasks.mjs +3 -3
- package/framework/skills/level-1-workflows/{phase-uiux → morph-phase-uiux}/SKILL.md +46 -11
- package/framework/skills/level-1-workflows/morph-scope-escalation/SKILL.md +97 -0
- package/framework/standards/STANDARDS.json +640 -88
- package/framework/standards/infrastructure/vercel/vercel-database.md +106 -0
- package/framework/standards/integration/mcp/mcp-tools.md +25 -7
- package/framework/templates/REGISTRY.json +1825 -1909
- package/framework/templates/context/CONTEXT-FEATURE.md +276 -276
- package/framework/templates/docs/onboarding.md +3 -7
- package/package.json +2 -7
- package/src/commands/agents/dispatch-agents.js +104 -6
- package/src/commands/mcp/mcp-setup.js +39 -2
- package/src/commands/phase/phase-reset.js +74 -0
- package/src/commands/project/doctor.js +34 -51
- package/src/commands/project/init.js +1 -1
- package/src/commands/project/status.js +2 -2
- package/src/commands/project/update.js +381 -365
- package/src/commands/project/worktree.js +154 -0
- package/src/commands/scope/escalate.js +215 -0
- package/src/commands/state/advance-phase.js +132 -68
- package/src/commands/state/approve.js +2 -2
- package/src/commands/state/index.js +7 -8
- package/src/commands/state/phase-runner.js +1 -1
- package/src/commands/state/state.js +61 -6
- package/src/commands/task/expand.js +100 -0
- package/src/commands/tasks/task.js +78 -99
- package/src/commands/templates/template-render.js +93 -173
- package/src/commands/trust/trust.js +26 -21
- package/src/core/paths/output-schema.js +19 -3
- package/src/core/state/phase-state-machine.js +7 -4
- package/src/core/state/state-manager.js +32 -57
- package/src/core/workflows/workflow-detector.js +9 -87
- package/src/lib/detectors/claude-config-detector.js +93 -347
- package/src/lib/detectors/design-system-detector.js +189 -189
- package/src/lib/detectors/index.js +155 -57
- package/src/lib/generators/context-generator.js +2 -2
- package/src/lib/installers/mcp-installer.js +37 -5
- package/src/lib/phase-chain/phase-validator.js +336 -0
- package/src/lib/scope/impact-analyzer.js +106 -0
- package/src/lib/stack/stack-profile.js +88 -0
- package/src/lib/tasks/task-classifier.js +16 -0
- package/src/lib/tasks/task-parser.js +1 -1
- package/src/lib/tasks/test-runner.js +77 -0
- package/src/lib/trust/trust-manager.js +32 -144
- package/src/lib/validators/shared/emit-validator-dispatch.js +64 -0
- package/src/lib/validators/spec-validator.js +58 -4
- package/src/lib/validators/validation-runner.js +23 -11
- package/src/scripts/setup-infra.js +255 -224
- package/src/utils/agents-installer.js +34 -14
- package/src/utils/banner.js +1 -1
- package/src/utils/claude-settings-manager.js +1 -1
- package/src/utils/file-copier.js +1 -1
- package/src/utils/hooks-installer.js +272 -8
- package/framework/hooks/dev/check-sync-health.js +0 -117
- package/framework/hooks/dev/guard-version-numbers.js +0 -57
- package/framework/hooks/dev/sync-standards-registry.js +0 -60
- package/framework/hooks/dev/sync-template-registry.js +0 -60
- package/framework/hooks/dev/validate-skill-format.js +0 -70
- package/framework/hooks/dev/validate-standard-format.js +0 -73
- package/framework/skills/level-1-workflows/phase-clarify/SKILL.md +0 -190
- package/framework/skills/level-1-workflows/phase-design/SKILL.md +0 -366
- package/framework/templates/meta-prompts/hops/hop-retry.md +0 -78
- package/framework/templates/meta-prompts/hops/hop-validation.md +0 -97
- package/framework/templates/meta-prompts/hops/hop-wrapper.md +0 -36
- package/framework/workflows/configs/design-impl.json +0 -49
- package/framework/workflows/configs/express.json +0 -45
- package/framework/workflows/configs/fast-track.json +0 -42
- package/framework/workflows/configs/full-morph.json +0 -79
- package/framework/workflows/configs/fusion.json +0 -39
- package/framework/workflows/configs/long-running.json +0 -33
- package/framework/workflows/configs/spec-only.json +0 -43
- package/framework/workflows/configs/ui-refresh.json +0 -49
- package/framework/workflows/configs/zero-touch.json +0 -82
- package/src/commands/project/index.js +0 -8
- package/src/commands/project/monitor.js +0 -295
- package/src/commands/project/tutorial.js +0 -115
- package/src/commands/state/validate-phase.js +0 -238
- package/src/commands/templates/generate-contracts.js +0 -445
- package/src/core/index.js +0 -10
- package/src/core/orchestrator.js +0 -171
- package/src/core/registry/command-registry.js +0 -28
- package/src/core/registry/index.js +0 -8
- package/src/core/registry/validator-registry.js +0 -204
- package/src/core/state/index.js +0 -8
- package/src/core/templates/index.js +0 -9
- package/src/core/templates/template-data-sources.js +0 -325
- package/src/core/templates/template-validator.js +0 -296
- package/src/core/workflows/index.js +0 -7
- package/src/generator/config-generator.js +0 -206
- package/src/generator/templates/config.json.template +0 -40
- package/src/generator/templates/project.md.template +0 -67
- package/src/lib/agents/micro-agent-factory.js +0 -161
- package/src/lib/analysis/complexity-analyzer.js +0 -441
- package/src/lib/analysis/index.js +0 -7
- package/src/lib/analytics/analytics-engine.js +0 -345
- package/src/lib/checkpoints/checkpoint-hooks.js +0 -298
- package/src/lib/checkpoints/index.js +0 -7
- package/src/lib/context/context-bundler.js +0 -241
- package/src/lib/context/context-optimizer.js +0 -212
- package/src/lib/context/context-tracker.js +0 -273
- package/src/lib/context/core-four-tracker.js +0 -201
- package/src/lib/context/mcp-optimizer.js +0 -200
- package/src/lib/detectors/config-detector.js +0 -223
- package/src/lib/detectors/standards-generator.js +0 -335
- package/src/lib/detectors/structure-detector.js +0 -275
- package/src/lib/execution/fusion-executor.js +0 -304
- package/src/lib/execution/parallel-executor.js +0 -270
- package/src/lib/hooks/stop-hook-executor.js +0 -286
- package/src/lib/hops/hop-composer.js +0 -221
- package/src/lib/monitor/agent-resolver.js +0 -144
- package/src/lib/monitor/renderer.js +0 -230
- package/src/lib/orchestration/index.js +0 -7
- package/src/lib/orchestration/team-orchestrator.js +0 -404
- package/src/lib/phase-chain/eligibility-checker.js +0 -243
- package/src/lib/threads/thread-coordinator.js +0 -238
- package/src/lib/threads/thread-manager.js +0 -317
- package/src/lib/tracking/artifact-trail.js +0 -202
- package/src/sanitizer/context-sanitizer.js +0 -221
- package/src/sanitizer/patterns.js +0 -163
- package/src/scanner/project-scanner.js +0 -242
- package/src/ui/diff-display.js +0 -91
- package/src/ui/interactive-wizard.js +0 -96
- package/src/ui/user-review.js +0 -211
- package/src/ui/wizard-questions.js +0 -188
- package/src/utils/color-utils.js +0 -70
- package/src/utils/process-handler.js +0 -97
- package/src/writer/file-writer.js +0 -86
- /package/framework/skills/level-0-meta/{brainstorming → morph-brainstorming}/references/proposal-example.md +0 -0
- /package/framework/skills/level-0-meta/{code-review → morph-code-review}/references/review-example.md +0 -0
- /package/framework/skills/level-0-meta/{code-review → morph-code-review}/references/review-guidelines.md +0 -0
- /package/framework/skills/level-0-meta/{code-review → morph-code-review}/scripts/scan-csharp.mjs +0 -0
- /package/framework/skills/level-0-meta/{code-review-nextjs → morph-code-review-nextjs}/references/review-example-nextjs.md +0 -0
- /package/framework/skills/level-0-meta/{code-review-nextjs → morph-code-review-nextjs}/scripts/scan-nextjs.mjs +0 -0
- /package/framework/skills/level-0-meta/{frontend-review → morph-frontend-review}/scripts/scan-accessibility.mjs +0 -0
- /package/framework/skills/level-0-meta/{post-implementation → morph-post-implementation}/scripts/detect-dev-server.mjs +0 -0
- /package/framework/skills/level-0-meta/{post-implementation → morph-post-implementation}/scripts/detect-stack.mjs +0 -0
- /package/framework/skills/level-0-meta/{terminal-title → morph-terminal-title}/scripts/set_title.sh +0 -0
- /package/framework/skills/level-1-workflows/{phase-clarify → morph-phase-clarify}/references/clarifications-example.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-design → morph-phase-design}/references/architecture-analysis-guide.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-design → morph-phase-design}/references/spec-authoring-guide.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-design → morph-phase-design}/references/spec-example.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-implement → morph-phase-implement}/references/recap-example.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-implement → morph-phase-implement}/references/vsa-implementation-guide.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/references/task-planning-patterns.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/references/tasks-example.md +0 -0
|
@@ -1,242 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview ProjectScanner - Scans project directory and collects context
|
|
3
|
-
* @module morph-spec/scanner/project-scanner
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { readFile, access } from 'fs/promises';
|
|
7
|
-
import { join, dirname, relative } from 'path';
|
|
8
|
-
import { readdir, stat } from 'fs/promises';
|
|
9
|
-
import { execSync } from 'child_process';
|
|
10
|
-
import { glob } from 'glob';
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* @typedef {import('../types/index.js').ProjectContext} ProjectContext
|
|
14
|
-
* @typedef {import('../types/index.js').PackageJson} PackageJson
|
|
15
|
-
* @typedef {import('../types/index.js').DirectoryStructure} DirectoryStructure
|
|
16
|
-
* @typedef {import('../types/index.js').InfraFiles} InfraFiles
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* ProjectScanner - Scans project directory and collects context for LLM analysis
|
|
21
|
-
* @class
|
|
22
|
-
*/
|
|
23
|
-
export class ProjectScanner {
|
|
24
|
-
/**
|
|
25
|
-
* Scan the project directory and collect complete context
|
|
26
|
-
* @param {string} cwd - Current working directory (absolute path)
|
|
27
|
-
* @returns {Promise<ProjectContext>}
|
|
28
|
-
*/
|
|
29
|
-
async scan(cwd) {
|
|
30
|
-
const [
|
|
31
|
-
packageJson,
|
|
32
|
-
csprojFiles,
|
|
33
|
-
solutionFile,
|
|
34
|
-
readme,
|
|
35
|
-
claudeMd,
|
|
36
|
-
structure,
|
|
37
|
-
infraFiles,
|
|
38
|
-
gitRemote
|
|
39
|
-
] = await Promise.all([
|
|
40
|
-
this.readPackageJson(cwd),
|
|
41
|
-
this.findCsprojFiles(cwd),
|
|
42
|
-
this.findSolutionFile(cwd),
|
|
43
|
-
this.readFileIfExists(join(cwd, 'README.md')),
|
|
44
|
-
this.readFileIfExists(join(cwd, 'CLAUDE.md')),
|
|
45
|
-
this.detectDirectoryStructure(cwd),
|
|
46
|
-
this.findInfraFiles(cwd),
|
|
47
|
-
this.getGitRemote(cwd)
|
|
48
|
-
]);
|
|
49
|
-
|
|
50
|
-
return {
|
|
51
|
-
cwd,
|
|
52
|
-
packageJson,
|
|
53
|
-
csprojFiles,
|
|
54
|
-
solutionFile,
|
|
55
|
-
readme,
|
|
56
|
-
claudeMd,
|
|
57
|
-
structure,
|
|
58
|
-
infraFiles,
|
|
59
|
-
gitRemote,
|
|
60
|
-
scannedAt: new Date()
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Read and parse package.json
|
|
66
|
-
* @param {string} cwd - Current working directory
|
|
67
|
-
* @returns {Promise<PackageJson|null>}
|
|
68
|
-
*/
|
|
69
|
-
async readPackageJson(cwd) {
|
|
70
|
-
try {
|
|
71
|
-
const packageJsonPath = join(cwd, 'package.json');
|
|
72
|
-
const content = await readFile(packageJsonPath, 'utf-8');
|
|
73
|
-
return JSON.parse(content);
|
|
74
|
-
} catch (error) {
|
|
75
|
-
// No package.json or parse error
|
|
76
|
-
return null;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Find all .csproj files recursively
|
|
82
|
-
* @param {string} cwd - Current working directory
|
|
83
|
-
* @returns {Promise<string[]>}
|
|
84
|
-
*/
|
|
85
|
-
async findCsprojFiles(cwd) {
|
|
86
|
-
try {
|
|
87
|
-
const pattern = '**/*.csproj';
|
|
88
|
-
const options = {
|
|
89
|
-
cwd,
|
|
90
|
-
ignore: ['**/node_modules/**', '**/bin/**', '**/obj/**', '**/.git/**'],
|
|
91
|
-
absolute: false
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
const files = await glob(pattern, options);
|
|
95
|
-
return files;
|
|
96
|
-
} catch (error) {
|
|
97
|
-
return [];
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Find solution file (.sln)
|
|
103
|
-
* @param {string} cwd - Current working directory
|
|
104
|
-
* @returns {Promise<string|null>}
|
|
105
|
-
*/
|
|
106
|
-
async findSolutionFile(cwd) {
|
|
107
|
-
try {
|
|
108
|
-
const pattern = '**/*.sln';
|
|
109
|
-
const options = {
|
|
110
|
-
cwd,
|
|
111
|
-
ignore: ['**/node_modules/**', '**/.git/**'],
|
|
112
|
-
absolute: false
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
const files = await glob(pattern, options);
|
|
116
|
-
return files.length > 0 ? files[0] : null;
|
|
117
|
-
} catch (error) {
|
|
118
|
-
return null;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Detect directory structure and patterns
|
|
124
|
-
* @param {string} cwd - Current working directory
|
|
125
|
-
* @returns {Promise<DirectoryStructure>}
|
|
126
|
-
*/
|
|
127
|
-
async detectDirectoryStructure(cwd) {
|
|
128
|
-
try {
|
|
129
|
-
const entries = await readdir(cwd, { withFileTypes: true });
|
|
130
|
-
const dirs = entries
|
|
131
|
-
.filter(entry => entry.isDirectory())
|
|
132
|
-
.map(entry => entry.name)
|
|
133
|
-
.filter(name => !name.startsWith('.') && name !== 'node_modules');
|
|
134
|
-
|
|
135
|
-
const hasSrc = dirs.includes('src');
|
|
136
|
-
const hasBackend = dirs.some(d => ['backend', 'server', 'api'].includes(d.toLowerCase()));
|
|
137
|
-
const hasFrontend = dirs.some(d => ['frontend', 'client', 'web', 'app'].includes(d.toLowerCase()));
|
|
138
|
-
const hasTests = dirs.some(d => ['test', 'tests', '__tests__'].includes(d.toLowerCase()));
|
|
139
|
-
|
|
140
|
-
// Detect pattern
|
|
141
|
-
let pattern = 'single-project';
|
|
142
|
-
if (hasBackend && hasFrontend) {
|
|
143
|
-
pattern = 'multi-stack';
|
|
144
|
-
} else if (dirs.length > 5 && dirs.some(d => d.includes('packages') || d.includes('apps'))) {
|
|
145
|
-
pattern = 'monorepo';
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
return {
|
|
149
|
-
hasSrc,
|
|
150
|
-
hasBackend,
|
|
151
|
-
hasFrontend,
|
|
152
|
-
hasTests,
|
|
153
|
-
topLevelDirs: dirs,
|
|
154
|
-
pattern
|
|
155
|
-
};
|
|
156
|
-
} catch (error) {
|
|
157
|
-
return {
|
|
158
|
-
hasSrc: false,
|
|
159
|
-
hasBackend: false,
|
|
160
|
-
hasFrontend: false,
|
|
161
|
-
hasTests: false,
|
|
162
|
-
topLevelDirs: [],
|
|
163
|
-
pattern: 'single-project'
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Find infrastructure files (Docker, Bicep, pipelines)
|
|
170
|
-
* @param {string} cwd - Current working directory
|
|
171
|
-
* @returns {Promise<InfraFiles>}
|
|
172
|
-
*/
|
|
173
|
-
async findInfraFiles(cwd) {
|
|
174
|
-
try {
|
|
175
|
-
const [dockerfiles, dockerComposeFiles, bicepFiles, pipelines] = await Promise.all([
|
|
176
|
-
glob('**/Dockerfile*', { cwd, ignore: ['**/node_modules/**', '**/.git/**'], absolute: false }),
|
|
177
|
-
glob('**/docker-compose*.yml', { cwd, ignore: ['**/node_modules/**', '**/.git/**'], absolute: false }),
|
|
178
|
-
glob('**/*.bicep', { cwd, ignore: ['**/node_modules/**', '**/.git/**'], absolute: false }),
|
|
179
|
-
glob('**/{azure-pipelines.yml,.github/workflows/*.yml,.gitlab-ci.yml}', {
|
|
180
|
-
cwd,
|
|
181
|
-
ignore: ['**/node_modules/**'],
|
|
182
|
-
absolute: false
|
|
183
|
-
})
|
|
184
|
-
]);
|
|
185
|
-
|
|
186
|
-
return {
|
|
187
|
-
dockerfiles,
|
|
188
|
-
dockerComposeFiles,
|
|
189
|
-
bicepFiles,
|
|
190
|
-
pipelines,
|
|
191
|
-
hasAzure: bicepFiles.length > 0 || pipelines.some(p => p.includes('azure')),
|
|
192
|
-
hasDocker: dockerfiles.length > 0 || dockerComposeFiles.length > 0,
|
|
193
|
-
hasDevOps: pipelines.length > 0
|
|
194
|
-
};
|
|
195
|
-
} catch (error) {
|
|
196
|
-
return {
|
|
197
|
-
dockerfiles: [],
|
|
198
|
-
dockerComposeFiles: [],
|
|
199
|
-
bicepFiles: [],
|
|
200
|
-
pipelines: [],
|
|
201
|
-
hasAzure: false,
|
|
202
|
-
hasDocker: false,
|
|
203
|
-
hasDevOps: false
|
|
204
|
-
};
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* Get git remote URL
|
|
210
|
-
* @param {string} cwd - Current working directory
|
|
211
|
-
* @returns {Promise<string|null>}
|
|
212
|
-
*/
|
|
213
|
-
async getGitRemote(cwd) {
|
|
214
|
-
try {
|
|
215
|
-
const remote = execSync('git remote get-url origin', {
|
|
216
|
-
cwd,
|
|
217
|
-
encoding: 'utf-8',
|
|
218
|
-
stdio: ['pipe', 'pipe', 'ignore']
|
|
219
|
-
}).trim();
|
|
220
|
-
|
|
221
|
-
return remote || null;
|
|
222
|
-
} catch (error) {
|
|
223
|
-
// Not a git repo or no remote
|
|
224
|
-
return null;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* Helper: Read file if it exists
|
|
230
|
-
* @param {string} filepath - Absolute file path
|
|
231
|
-
* @returns {Promise<string|null>}
|
|
232
|
-
*/
|
|
233
|
-
async readFileIfExists(filepath) {
|
|
234
|
-
try {
|
|
235
|
-
await access(filepath);
|
|
236
|
-
const content = await readFile(filepath, 'utf-8');
|
|
237
|
-
return content;
|
|
238
|
-
} catch (error) {
|
|
239
|
-
return null;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
}
|
package/src/ui/diff-display.js
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Diff Display - Shows diff between current and generated configs
|
|
3
|
-
* @module morph-spec/ui/diff-display
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { diffLines } from 'diff';
|
|
7
|
-
import chalk from 'chalk';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Display diff between two text strings
|
|
11
|
-
* @param {string} oldContent - Original content
|
|
12
|
-
* @param {string} newContent - New content
|
|
13
|
-
* @param {string} filename - Filename for display
|
|
14
|
-
*/
|
|
15
|
-
export function displayDiff(oldContent, newContent, filename) {
|
|
16
|
-
console.log(chalk.bold(`\n📝 Changes in ${filename}:\n`));
|
|
17
|
-
|
|
18
|
-
const diff = diffLines(oldContent, newContent);
|
|
19
|
-
|
|
20
|
-
diff.forEach(part => {
|
|
21
|
-
const color = part.added ? chalk.green : part.removed ? chalk.red : chalk.gray;
|
|
22
|
-
const prefix = part.added ? '+ ' : part.removed ? '- ' : ' ';
|
|
23
|
-
|
|
24
|
-
part.value.split('\n').forEach((line, index) => {
|
|
25
|
-
if (line || index < part.value.split('\n').length - 1) {
|
|
26
|
-
console.log(color(prefix + line));
|
|
27
|
-
}
|
|
28
|
-
});
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
console.log(); // Empty line after diff
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Display side-by-side summary of changes
|
|
36
|
-
* @param {Object} oldConfig - Old config object
|
|
37
|
-
* @param {Object} newConfig - New config object
|
|
38
|
-
*/
|
|
39
|
-
export function displayConfigSummary(oldConfig, newConfig) {
|
|
40
|
-
console.log(chalk.bold('\n📊 Configuration Summary:\n'));
|
|
41
|
-
|
|
42
|
-
const fields = [
|
|
43
|
-
{ key: 'name', label: 'Project Name' },
|
|
44
|
-
{ key: 'type', label: 'Project Type' },
|
|
45
|
-
{ key: 'description', label: 'Description' },
|
|
46
|
-
{ key: 'stack.backend.tech', label: 'Backend' },
|
|
47
|
-
{ key: 'stack.frontend.tech', label: 'Frontend' },
|
|
48
|
-
{ key: 'stack.database.tech', label: 'Database' },
|
|
49
|
-
{ key: 'architecture', label: 'Architecture' }
|
|
50
|
-
];
|
|
51
|
-
|
|
52
|
-
fields.forEach(({ key, label }) => {
|
|
53
|
-
const oldValue = getNestedValue(oldConfig, key) || chalk.gray('(not set)');
|
|
54
|
-
const newValue = getNestedValue(newConfig, key) || chalk.gray('(not set)');
|
|
55
|
-
|
|
56
|
-
const changed = oldValue !== newValue;
|
|
57
|
-
const color = changed ? chalk.yellow : chalk.white;
|
|
58
|
-
|
|
59
|
-
console.log(
|
|
60
|
-
color(` ${label.padEnd(20)}: `) +
|
|
61
|
-
chalk.dim(String(oldValue).padEnd(30)) +
|
|
62
|
-
chalk.bold(' → ') +
|
|
63
|
-
color(newValue)
|
|
64
|
-
);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
console.log();
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Get nested value from object by dot notation key
|
|
72
|
-
* @param {Object} obj - Object to query
|
|
73
|
-
* @param {string} key - Dot notation key (e.g., 'stack.backend.tech')
|
|
74
|
-
* @returns {any} Value or null
|
|
75
|
-
*/
|
|
76
|
-
function getNestedValue(obj, key) {
|
|
77
|
-
if (!obj) return null;
|
|
78
|
-
|
|
79
|
-
const keys = key.split('.');
|
|
80
|
-
let current = obj;
|
|
81
|
-
|
|
82
|
-
for (const k of keys) {
|
|
83
|
-
if (current && typeof current === 'object' && k in current) {
|
|
84
|
-
current = current[k];
|
|
85
|
-
} else {
|
|
86
|
-
return null;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return current;
|
|
91
|
-
}
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Interactive Wizard - Manual configuration when LLM fails
|
|
3
|
-
* @module morph-spec/ui/interactive-wizard
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import inquirer from 'inquirer';
|
|
7
|
-
import chalk from 'chalk';
|
|
8
|
-
import { getWizardQuestions, mapAnswersToConfig } from './wizard-questions.js';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* @typedef {import('../types/index.js').ProjectConfig} ProjectConfig
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* InteractiveWizard - Guides user through manual project configuration
|
|
16
|
-
* @class
|
|
17
|
-
*/
|
|
18
|
-
export class InteractiveWizard {
|
|
19
|
-
/**
|
|
20
|
-
* Run interactive wizard to collect project configuration
|
|
21
|
-
* @returns {Promise<ProjectConfig>}
|
|
22
|
-
*/
|
|
23
|
-
async run() {
|
|
24
|
-
console.log(chalk.bold.cyan('\n🧙 Interactive Project Configuration Wizard\n'));
|
|
25
|
-
console.log(chalk.dim(' Answer 7 questions to configure your project\n'));
|
|
26
|
-
|
|
27
|
-
const questions = getWizardQuestions();
|
|
28
|
-
const answers = await inquirer.prompt(questions);
|
|
29
|
-
|
|
30
|
-
// Map answers to ProjectConfig
|
|
31
|
-
const config = mapAnswersToConfig(answers);
|
|
32
|
-
|
|
33
|
-
// Show summary
|
|
34
|
-
this.displaySummary(config);
|
|
35
|
-
|
|
36
|
-
// Confirm before proceeding
|
|
37
|
-
const { confirmed } = await inquirer.prompt([
|
|
38
|
-
{
|
|
39
|
-
type: 'confirm',
|
|
40
|
-
name: 'confirmed',
|
|
41
|
-
message: 'Is this configuration correct?',
|
|
42
|
-
default: true
|
|
43
|
-
}
|
|
44
|
-
]);
|
|
45
|
-
|
|
46
|
-
if (!confirmed) {
|
|
47
|
-
console.log(chalk.yellow('\n❌ Configuration canceled. Please run the wizard again.\n'));
|
|
48
|
-
process.exit(0);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
return config;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Display summary of collected configuration
|
|
56
|
-
* @param {ProjectConfig} config - Project configuration
|
|
57
|
-
*/
|
|
58
|
-
displaySummary(config) {
|
|
59
|
-
console.log(chalk.bold.green('\n✅ Configuration Summary:\n'));
|
|
60
|
-
|
|
61
|
-
console.log(chalk.bold(' Name: ') + chalk.cyan(config.name));
|
|
62
|
-
console.log(chalk.bold(' Type: ') + chalk.yellow(config.type));
|
|
63
|
-
console.log(chalk.bold(' Description: ') + chalk.white(config.description));
|
|
64
|
-
console.log();
|
|
65
|
-
|
|
66
|
-
console.log(chalk.bold(' Stack:'));
|
|
67
|
-
if (config.stack.frontend) {
|
|
68
|
-
console.log(chalk.gray(' Frontend: ') + chalk.white(`${config.stack.frontend.tech} ${config.stack.frontend.version}`));
|
|
69
|
-
} else {
|
|
70
|
-
console.log(chalk.gray(' Frontend: ') + chalk.dim('None (backend-only)'));
|
|
71
|
-
}
|
|
72
|
-
console.log(chalk.gray(' Backend: ') + chalk.white(`${config.stack.backend.tech} ${config.stack.backend.version}`));
|
|
73
|
-
if (config.stack.database) {
|
|
74
|
-
console.log(chalk.gray(' Database: ') + chalk.white(`${config.stack.database.tech} ${config.stack.database.version}`));
|
|
75
|
-
} else {
|
|
76
|
-
console.log(chalk.gray(' Database: ') + chalk.dim('None'));
|
|
77
|
-
}
|
|
78
|
-
if (config.stack.hosting) {
|
|
79
|
-
console.log(chalk.gray(' Hosting: ') + chalk.white(config.stack.hosting));
|
|
80
|
-
}
|
|
81
|
-
console.log();
|
|
82
|
-
|
|
83
|
-
console.log(chalk.bold(' Architecture: ') + chalk.magenta(config.architecture));
|
|
84
|
-
console.log();
|
|
85
|
-
|
|
86
|
-
// Infrastructure flags
|
|
87
|
-
const flags = [];
|
|
88
|
-
if (config.hasAzure) flags.push(chalk.blue('Azure'));
|
|
89
|
-
if (config.hasDocker) flags.push(chalk.cyan('Docker'));
|
|
90
|
-
|
|
91
|
-
if (flags.length > 0) {
|
|
92
|
-
console.log(chalk.bold(' Infrastructure: ') + flags.join(' • '));
|
|
93
|
-
console.log();
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
package/src/ui/user-review.js
DELETED
|
@@ -1,211 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview UserReview - Prompts user for approval of generated configs
|
|
3
|
-
* @module morph-spec/ui/user-review
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import inquirer from 'inquirer';
|
|
7
|
-
import chalk from 'chalk';
|
|
8
|
-
import { displayDiff, displayConfigSummary } from './diff-display.js';
|
|
9
|
-
import { writeFile, readFile, access } from 'fs/promises';
|
|
10
|
-
import { join } from 'path';
|
|
11
|
-
import { execSync } from 'child_process';
|
|
12
|
-
import { tmpdir } from 'os';
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* @typedef {import('../types/index.js').GeneratedConfigs} GeneratedConfigs
|
|
16
|
-
* @typedef {import('../types/index.js').ProjectConfig} ProjectConfig
|
|
17
|
-
* @typedef {import('../types/index.js').ApprovalResponse} ApprovalResponse
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* UserReview - Handles user review and approval of generated configs
|
|
22
|
-
* @class
|
|
23
|
-
*/
|
|
24
|
-
export class UserReview {
|
|
25
|
-
/**
|
|
26
|
-
* Prompt user for approval of generated configs
|
|
27
|
-
* @param {GeneratedConfigs} configs - Generated configs
|
|
28
|
-
* @param {ProjectConfig} projectConfig - Detected project config
|
|
29
|
-
* @param {Object} [existingConfigs] - Existing configs (if updating)
|
|
30
|
-
* @returns {Promise<ApprovalResponse>}
|
|
31
|
-
*/
|
|
32
|
-
async promptForApproval(configs, projectConfig, existingConfigs = null) {
|
|
33
|
-
console.log(chalk.bold.cyan('\n🔍 Auto-Detected Project Configuration\n'));
|
|
34
|
-
|
|
35
|
-
// Display summary
|
|
36
|
-
this.displayPreview(projectConfig);
|
|
37
|
-
|
|
38
|
-
// If updating existing configs, show diff
|
|
39
|
-
if (existingConfigs) {
|
|
40
|
-
if (existingConfigs.projectMd) {
|
|
41
|
-
displayDiff(existingConfigs.projectMd, configs.projectMd, 'project.md');
|
|
42
|
-
}
|
|
43
|
-
if (existingConfigs.configJson) {
|
|
44
|
-
try {
|
|
45
|
-
const oldConfig = JSON.parse(existingConfigs.configJson);
|
|
46
|
-
displayConfigSummary(oldConfig, configs.configObject);
|
|
47
|
-
} catch (error) {
|
|
48
|
-
// Couldn't parse old config, skip summary
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Prompt for action
|
|
54
|
-
const { action } = await inquirer.prompt([
|
|
55
|
-
{
|
|
56
|
-
type: 'list',
|
|
57
|
-
name: 'action',
|
|
58
|
-
message: 'What would you like to do?',
|
|
59
|
-
choices: [
|
|
60
|
-
{ name: '✅ Approve and save configs', value: 'approve' },
|
|
61
|
-
{ name: '✏️ Edit configs before saving', value: 'edit' },
|
|
62
|
-
{ name: '❌ Cancel and exit', value: 'cancel' }
|
|
63
|
-
],
|
|
64
|
-
default: 'approve'
|
|
65
|
-
}
|
|
66
|
-
]);
|
|
67
|
-
|
|
68
|
-
switch (action) {
|
|
69
|
-
case 'approve':
|
|
70
|
-
return { action: 'approve' };
|
|
71
|
-
|
|
72
|
-
case 'edit':
|
|
73
|
-
const editedConfigs = await this.openInEditor(configs);
|
|
74
|
-
return { action: 'approve', editedConfigs };
|
|
75
|
-
|
|
76
|
-
case 'cancel':
|
|
77
|
-
const { reason } = await inquirer.prompt([
|
|
78
|
-
{
|
|
79
|
-
type: 'input',
|
|
80
|
-
name: 'reason',
|
|
81
|
-
message: 'Why are you canceling? (optional)',
|
|
82
|
-
default: 'User canceled'
|
|
83
|
-
}
|
|
84
|
-
]);
|
|
85
|
-
return { action: 'cancel', cancelReason: reason };
|
|
86
|
-
|
|
87
|
-
default:
|
|
88
|
-
return { action: 'cancel', cancelReason: 'Unknown action' };
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Display preview of detected configuration
|
|
94
|
-
* @param {ProjectConfig} projectConfig - Detected project config
|
|
95
|
-
*/
|
|
96
|
-
displayPreview(projectConfig) {
|
|
97
|
-
console.log(chalk.bold(' Name: ') + chalk.cyan(projectConfig.name));
|
|
98
|
-
console.log(chalk.bold(' Type: ') + chalk.yellow(projectConfig.type));
|
|
99
|
-
console.log(chalk.bold(' Description: ') + chalk.white(projectConfig.description));
|
|
100
|
-
console.log();
|
|
101
|
-
|
|
102
|
-
console.log(chalk.bold(' Stack:'));
|
|
103
|
-
if (projectConfig.stack.frontend) {
|
|
104
|
-
console.log(chalk.gray(' Frontend: ') + chalk.white(`${projectConfig.stack.frontend.tech} ${projectConfig.stack.frontend.version}`));
|
|
105
|
-
}
|
|
106
|
-
console.log(chalk.gray(' Backend: ') + chalk.white(`${projectConfig.stack.backend.tech} ${projectConfig.stack.backend.version}`));
|
|
107
|
-
if (projectConfig.stack.database) {
|
|
108
|
-
console.log(chalk.gray(' Database: ') + chalk.white(`${projectConfig.stack.database.tech} ${projectConfig.stack.database.version}`));
|
|
109
|
-
}
|
|
110
|
-
if (projectConfig.stack.hosting) {
|
|
111
|
-
console.log(chalk.gray(' Hosting: ') + chalk.white(projectConfig.stack.hosting));
|
|
112
|
-
}
|
|
113
|
-
console.log();
|
|
114
|
-
|
|
115
|
-
console.log(chalk.bold(' Architecture: ') + chalk.magenta(projectConfig.architecture));
|
|
116
|
-
console.log(chalk.bold(' Conventions: ') + chalk.white(projectConfig.conventions));
|
|
117
|
-
console.log();
|
|
118
|
-
|
|
119
|
-
// Infrastructure flags
|
|
120
|
-
const flags = [];
|
|
121
|
-
if (projectConfig.hasAzure) flags.push(chalk.blue('Azure'));
|
|
122
|
-
if (projectConfig.hasDocker) flags.push(chalk.cyan('Docker'));
|
|
123
|
-
if (projectConfig.hasDevOps) flags.push(chalk.green('CI/CD'));
|
|
124
|
-
|
|
125
|
-
if (flags.length > 0) {
|
|
126
|
-
console.log(chalk.bold(' Infrastructure: ') + flags.join(' • '));
|
|
127
|
-
console.log();
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Confidence and warnings
|
|
131
|
-
const confidenceColor = projectConfig.confidence >= 90 ? chalk.green : projectConfig.confidence >= 70 ? chalk.yellow : chalk.red;
|
|
132
|
-
console.log(chalk.bold(' Confidence: ') + confidenceColor(`${projectConfig.confidence}%`));
|
|
133
|
-
|
|
134
|
-
if (projectConfig.warnings && projectConfig.warnings.length > 0) {
|
|
135
|
-
console.log(chalk.bold.yellow('\n ⚠️ Warnings:'));
|
|
136
|
-
projectConfig.warnings.forEach(warning => {
|
|
137
|
-
console.log(chalk.yellow(` • ${warning}`));
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
console.log();
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Display diff between current and generated configs
|
|
146
|
-
* @param {string} current - Current config content
|
|
147
|
-
* @param {string} generated - Generated config content
|
|
148
|
-
*/
|
|
149
|
-
displayDiff(current, generated) {
|
|
150
|
-
displayDiff(current, generated, 'config');
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Open configs in editor for manual editing
|
|
155
|
-
* @param {GeneratedConfigs} configs - Configs to edit
|
|
156
|
-
* @returns {Promise<GeneratedConfigs>} Edited configs
|
|
157
|
-
*/
|
|
158
|
-
async openInEditor(configs) {
|
|
159
|
-
console.log(chalk.cyan('\n📝 Opening configs in editor...\n'));
|
|
160
|
-
|
|
161
|
-
// Write to temp files
|
|
162
|
-
const tempDir = tmpdir();
|
|
163
|
-
const projectMdPath = join(tempDir, 'morph-project.md');
|
|
164
|
-
const configJsonPath = join(tempDir, 'morph-config.json');
|
|
165
|
-
|
|
166
|
-
await Promise.all([
|
|
167
|
-
writeFile(projectMdPath, configs.projectMd, 'utf-8'),
|
|
168
|
-
writeFile(configJsonPath, configs.configJson, 'utf-8')
|
|
169
|
-
]);
|
|
170
|
-
|
|
171
|
-
// Get editor from environment or use default
|
|
172
|
-
const editor = process.env.EDITOR || process.env.VISUAL || 'nano';
|
|
173
|
-
|
|
174
|
-
console.log(chalk.dim(` Using editor: ${editor}`));
|
|
175
|
-
console.log(chalk.dim(` Files: ${projectMdPath}, ${configJsonPath}`));
|
|
176
|
-
console.log(chalk.yellow('\n Press ENTER when done editing...'));
|
|
177
|
-
|
|
178
|
-
// Open editor (blocking)
|
|
179
|
-
try {
|
|
180
|
-
execSync(`${editor} "${projectMdPath}" "${configJsonPath}"`, {
|
|
181
|
-
stdio: 'inherit'
|
|
182
|
-
});
|
|
183
|
-
} catch (error) {
|
|
184
|
-
console.log(chalk.yellow('\n⚠️ Editor exited with error, using original configs\n'));
|
|
185
|
-
return configs;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// Read edited files
|
|
189
|
-
const [editedProjectMd, editedConfigJson] = await Promise.all([
|
|
190
|
-
readFile(projectMdPath, 'utf-8'),
|
|
191
|
-
readFile(configJsonPath, 'utf-8')
|
|
192
|
-
]);
|
|
193
|
-
|
|
194
|
-
// Parse config.json
|
|
195
|
-
let editedConfigObject;
|
|
196
|
-
try {
|
|
197
|
-
editedConfigObject = JSON.parse(editedConfigJson);
|
|
198
|
-
} catch (error) {
|
|
199
|
-
console.log(chalk.red('\n❌ Edited config.json is not valid JSON. Using original.\n'));
|
|
200
|
-
return configs;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
console.log(chalk.green('\n✅ Configs edited successfully\n'));
|
|
204
|
-
|
|
205
|
-
return {
|
|
206
|
-
projectMd: editedProjectMd,
|
|
207
|
-
configJson: editedConfigJson,
|
|
208
|
-
configObject: editedConfigObject
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
}
|