@polymorphism-tech/morph-spec 4.3.5 → 4.3.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polymorphism-tech/morph-spec",
3
- "version": "4.3.5",
3
+ "version": "4.3.7",
4
4
  "description": "MORPH-SPEC: AI-First development framework with validation pipeline and multi-stack support",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -48,7 +48,6 @@
48
48
  "docs:serve": "npx http-server docs/api -p 8080 -o"
49
49
  },
50
50
  "dependencies": {
51
- "@polymorphism-tech/morph-spec": "^4.3.1",
52
51
  "ajv": "^8.12.0",
53
52
  "ajv-formats": "^3.0.1",
54
53
  "chalk": "^5.3.0",
@@ -9,9 +9,11 @@ import { logger } from '../../utils/logger.js';
9
9
  import {
10
10
  getContentDir,
11
11
  copyDirectory,
12
+ copyFile,
12
13
  pathExists,
13
14
  ensureDir,
14
15
  createDirectoryLink,
16
+ createSymlink,
15
17
  readJson,
16
18
  writeJson
17
19
  } from '../../utils/file-copier.js';
@@ -204,54 +206,94 @@ export async function updateCommand(options) {
204
206
  await copyDirectory(standardsSrc, standardsDest);
205
207
  }
206
208
 
207
- // Update agents.json (sourced from framework root .morph/config/ — canonical single source of truth)
209
+ // Update agents.json (sourced from framework/ — canonical single source of truth)
208
210
  updateSpinner.text = 'Updating agents configuration...';
209
- const agentsSrc = join(__dirname, '..', '..', '..', '.morph', 'config', 'agents.json');
211
+ const agentsSrc = join(__dirname, '..', '..', '..', 'framework', 'agents.json');
210
212
  const agentsDest = join(morphPath, 'config', 'agents.json');
211
213
  if (await pathExists(agentsSrc)) {
212
- await copyDirectory(agentsSrc, agentsDest);
214
+ await copyFile(agentsSrc, agentsDest);
215
+ }
216
+
217
+ // Update azure-pricing files
218
+ updateSpinner.text = 'Updating Azure pricing data...';
219
+ const configDir = join(morphPath, 'config');
220
+ const pricingSrc = join(contentDir, '.morph', 'config', 'azure-pricing.json');
221
+ const pricingDest = join(configDir, 'azure-pricing.json');
222
+ if (await pathExists(pricingSrc)) {
223
+ await copyFile(pricingSrc, pricingDest);
224
+ }
225
+ const pricingSchemaSrc = join(contentDir, '.morph', 'config', 'azure-pricing.schema.json');
226
+ const pricingSchemaDest = join(configDir, 'azure-pricing.schema.json');
227
+ if (await pathExists(pricingSchemaSrc)) {
228
+ await copyFile(pricingSchemaSrc, pricingSchemaDest);
213
229
  }
214
230
 
215
231
  // Update .claude commands and skills
216
- // Source: framework root .claude/ (canonical for all stacks)
217
- updateSpinner.text = 'Updating Claude commands and skills...';
218
- const claudeSrc = join(__dirname, '..', '..', '..', '.claude');
232
+ // Source: framework/ (canonical for all stacks)
233
+ updateSpinner.text = 'Setting up Claude Code integration...';
234
+ const frameworkDir = join(__dirname, '..', '..', '..', 'framework');
219
235
  const claudeDest = join(targetPath, '.claude');
220
- let claudeUpdated = false;
221
- if (await pathExists(claudeSrc)) {
222
- // Copy commands (always copy, these are small)
223
- const commandsSrc = join(claudeSrc, 'commands');
224
- const commandsDest = join(claudeDest, 'commands');
225
- if (await pathExists(commandsSrc)) {
226
- await copyDirectory(commandsSrc, commandsDest);
227
- claudeUpdated = true;
228
-
229
- // Clean up stale command files moved to skills/workflows/ in v2.4+
230
- const staleCommands = [
231
- 'morph-setup.md', 'morph-uiux.md', 'morph-design.md',
232
- 'morph-clarify.md', 'morph-tasks.md'
233
- ];
234
- for (const file of staleCommands) {
235
- const stalePath = join(commandsDest, file);
236
- if (await pathExists(stalePath)) {
237
- await fs.remove(stalePath);
238
- }
239
- }
240
- } else {
241
- logger.warn(' ⚠ .claude/commands/ source missing — commands not updated');
242
- }
243
236
 
244
- // Update skills using directory links per category
245
- const skillsSrc = join(claudeSrc, 'skills');
237
+ let symlinkCount = 0;
238
+ let copyCount = 0;
239
+ let commandsCopied = false;
240
+
241
+ // Copy commands directory (slash commands): framework/commands/ → .claude/commands/
242
+ const commandsSrc = join(frameworkDir, 'commands');
243
+ const commandsDest = join(claudeDest, 'commands');
244
+ if (await pathExists(commandsSrc)) {
245
+ await copyDirectory(commandsSrc, commandsDest);
246
+ commandsCopied = true;
247
+ } else {
248
+ logger.warn(' ⚠ framework/commands/ source missing — commands not updated');
249
+ }
250
+
251
+ // Create directory links for skill categories (or copy if linking fails)
252
+ // Source: framework/skills/ → .claude/skills/
253
+ {
254
+ const skillsSrc = join(frameworkDir, 'skills');
246
255
  const skillsDest = join(claudeDest, 'skills');
256
+
247
257
  if (await pathExists(skillsSrc)) {
248
258
  await ensureDir(skillsDest);
259
+
249
260
  const entries = await fs.readdir(skillsSrc, { withFileTypes: true });
261
+
262
+ let linkedCategories = 0;
263
+ let copiedCategories = 0;
264
+ let totalSkillFiles = 0;
265
+
266
+ // Link category directories (specialists/, infra/, checklists/, etc.)
250
267
  for (const entry of entries) {
251
268
  if (entry.isDirectory()) {
252
- await createDirectoryLink(join(skillsSrc, entry.name), join(skillsDest, entry.name));
269
+ const categorySrc = join(skillsSrc, entry.name);
270
+ const categoryDest = join(skillsDest, entry.name);
271
+
272
+ // Count .md files for reporting
273
+ const categoryEntries = await fs.readdir(categorySrc, { withFileTypes: true });
274
+ const mdFiles = categoryEntries.filter(e => e.isFile() && e.name.endsWith('.md'));
275
+ totalSkillFiles += mdFiles.length;
276
+
277
+ const result = await createDirectoryLink(categorySrc, categoryDest);
278
+ if (result === 'copy') {
279
+ copiedCategories++;
280
+ } else {
281
+ linkedCategories++;
282
+ }
283
+ } else if (entry.isFile() && entry.name.endsWith('.md')) {
284
+ // Handle .md files in skills root
285
+ totalSkillFiles++;
286
+ const result = await createSymlink(join(skillsSrc, entry.name), join(skillsDest, entry.name), 'file');
287
+ if (result === 'symlink') {
288
+ linkedCategories++;
289
+ } else {
290
+ copiedCategories++;
291
+ }
253
292
  }
254
293
  }
294
+
295
+ symlinkCount = linkedCategories > 0 ? totalSkillFiles : 0;
296
+ copyCount = linkedCategories === 0 ? totalSkillFiles : 0;
255
297
  }
256
298
  }
257
299
 
@@ -291,7 +333,17 @@ export async function updateCommand(options) {
291
333
  if (updateTemplates) logger.dim(' ✓ .morph/templates/');
292
334
  if (updateStandards) logger.dim(' ✓ .morph/standards/');
293
335
  logger.dim(' ✓ .morph/config/agents.json');
294
- if (claudeUpdated) logger.dim(' ✓ .claude/commands/ and .claude/skills/');
336
+ logger.dim(' ✓ .morph/config/azure-pricing.json');
337
+ if (commandsCopied) logger.dim(' ✓ .claude/commands/');
338
+
339
+ if (symlinkCount > 0) {
340
+ const linkType = process.platform === 'win32' ? 'junction-linked' : 'symlinked';
341
+ logger.dim(` ✓ .claude/skills/ (${symlinkCount} ${linkType})`);
342
+ } else if (copyCount > 0) {
343
+ logger.dim(` ✓ .claude/skills/ (${copyCount} copied)`);
344
+ logger.warn(` ⚠ Directory links not supported (copied instead). Skills won't auto-update.`);
345
+ }
346
+
295
347
  logger.dim(' ✓ .claude/settings.local.json (agent-teams hooks)');
296
348
  logger.dim(' ✓ CLAUDE.md');
297
349
  logger.blank();