@polymorphism-tech/morph-spec 7.7.1 → 7.7.3

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 CHANGED
@@ -3,7 +3,7 @@
3
3
  > Spec-driven development framework for multi-stack projects. Turns feature requests into implementation-ready code through structured, AI-orchestrated phases.
4
4
 
5
5
  **Package:** `@polymorphism-tech/morph-spec`
6
- **Version:** 7.7.1
6
+ **Version:** 7.7.3
7
7
  **Requires:** Node.js 22+, Claude Code
8
8
 
9
9
  ---
@@ -365,4 +365,4 @@ Code generated by morph-spec (contracts, templates, implementation output) belon
365
365
 
366
366
  ---
367
367
 
368
- *morph-spec v7.7.1 by [Polymorphism Tech](https://polymorphism.tech)*
368
+ *morph-spec v7.7.3 by [Polymorphism Tech](https://polymorphism.tech)*
package/bin/morph-spec.js CHANGED
@@ -14,6 +14,7 @@ import chalk from 'chalk';
14
14
  import { fileURLToPath } from 'url';
15
15
  import { dirname, join } from 'path';
16
16
  import { readFileSync } from 'fs';
17
+ import { renderLogo } from '../src/utils/banner.js';
17
18
 
18
19
  // === Project / install ===
19
20
  import { initCommand } from '../src/commands/project/init.js';
@@ -52,17 +53,9 @@ import { dashboardCommand } from '../src/commands/dashboard/dashboard.js';
52
53
  const __dirname = dirname(fileURLToPath(import.meta.url));
53
54
  const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8'));
54
55
 
55
- const banner = chalk.cyan(`
56
- ███╗ ███╗ ██████╗ ██████╗ ██████╗ ██╗ ██╗
57
- ████╗ ████║██╔═══██╗██╔══██╗██╔══██╗██║ ██║
58
- ██╔████╔██║██║ ██║██████╔╝██████╔╝███████║
59
- ██║╚██╔╝██║██║ ██║██╔══██╗██╔═══╝ ██╔══██║
60
- ██║ ╚═╝ ██║╚██████╔╝██║ ██║██║ ██║ ██║
61
- ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝
62
-
63
- Spec-driven development harness for Claude Code
64
- by Polymorphism Tech | v${pkg.version}
65
- `);
56
+ const banner = `${renderLogo(pkg.version)}
57
+ ${chalk.dim('Spec-driven development harness for Claude Code — by Polymorphism Tech')}
58
+ `;
66
59
 
67
60
  program
68
61
  .name('morph-spec')
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "morph-spec",
3
- "version": "7.7.1",
3
+ "version": "7.7.3",
4
4
  "displayName": "MORPH-SPEC Framework",
5
5
  "description": "Spec-driven development with 38 agents and 8-phase workflow for .NET/Blazor/Next.js/Azure",
6
6
  "publisher": "polymorphism-tech",
@@ -196,4 +196,4 @@ These files are never edited directly. Use CLI commands or `morph-spec update` i
196
196
 
197
197
  ---
198
198
 
199
- *morph-spec v7.7.1 by Polymorphism Tech*
199
+ *morph-spec v7.7.3 by Polymorphism Tech*
@@ -197,4 +197,4 @@ morph-spec doctor
197
197
 
198
198
  ---
199
199
 
200
- *morph-spec v7.7.1 by Polymorphism Tech*
200
+ *morph-spec v7.7.3 by Polymorphism Tech*
@@ -4,7 +4,7 @@ description: Guia a criação de uma landing page nova ponta-a-ponta a partir do
4
4
  version: core
5
5
  when-to-invoke: ao iniciar um novo projeto de LP com o template Umbraco genérico
6
6
  user-invokable: true
7
- cliVersion: "7.7.1"
7
+ cliVersion: "7.7.3"
8
8
  ---
9
9
 
10
10
  # create-umbraco-lp
@@ -4,7 +4,7 @@ description: Migra uma landing page existente (React/Vite, Next.js estático ou
4
4
  version: core
5
5
  when-to-invoke: ao receber instrução "use /migrate-lp-to-umbraco {projeto}" do usuário, ou quando o usuário descrever necessidade de migrar uma LP para Umbraco
6
6
  user-invokable: true
7
- cliVersion: "7.7.1"
7
+ cliVersion: "7.7.3"
8
8
  ---
9
9
 
10
10
  # migrate-lp-to-umbraco
@@ -4,7 +4,7 @@ description: Orquestra a execução autônoma de tasks de uma feature morph-spec
4
4
  version: core
5
5
  when-to-invoke: após Gate 2 aprovado, ao receber instrução "use /morph-apply" ou "implementar feature"
6
6
  user-invokable: true
7
- cliVersion: "7.7.1"
7
+ cliVersion: "7.7.3"
8
8
  ---
9
9
 
10
10
  # morph-apply
@@ -4,7 +4,7 @@ description: Entende o negócio, os usuários e a arquitetura antes de qualquer
4
4
  version: core
5
5
  when-to-invoke: ao receber um novo request de feature, antes de morph-plan
6
6
  user-invokable: true
7
- cliVersion: "7.7.1"
7
+ cliVersion: "7.7.3"
8
8
  ---
9
9
 
10
10
  # morph-brainstorming
@@ -4,7 +4,7 @@ description: Avalia a qualidade de uma tarefa ou fase com rubricas graduadas 0-1
4
4
  version: core
5
5
  when-to-invoke: ao completar uma tarefa ou ao finalizar uma fase de implementação
6
6
  user-invokable: false
7
- cliVersion: "7.7.1"
7
+ cliVersion: "7.7.3"
8
8
  ---
9
9
 
10
10
  # morph-eval
@@ -4,7 +4,7 @@ description: Guia a execução de cada tarefa de implementação com standard e
4
4
  version: core
5
5
  when-to-invoke: ao iniciar cada task de implementação listada em tasks.json
6
6
  user-invokable: false
7
- cliVersion: "7.7.1"
7
+ cliVersion: "7.7.3"
8
8
  ---
9
9
 
10
10
  # morph-implement
@@ -4,7 +4,7 @@ description: Guia a produção dos três artefatos de planejamento de uma featur
4
4
  version: core
5
5
  when-to-invoke: ao iniciar a fase plan de uma feature, após o Gate 1 (proposal aprovado)
6
6
  user-invokable: true
7
- cliVersion: "7.7.1"
7
+ cliVersion: "7.7.3"
8
8
  ---
9
9
 
10
10
  # morph-plan
@@ -4,7 +4,7 @@ description: Orquestra a fase de proposta+plano de uma feature morph-spec. Encad
4
4
  version: core
5
5
  when-to-invoke: ao receber instrução "use /morph-proposal" do usuário, ou ao iniciar uma nova feature
6
6
  user-invokable: true
7
- cliVersion: "7.7.1"
7
+ cliVersion: "7.7.3"
8
8
  ---
9
9
 
10
10
  # morph-proposal
@@ -4,7 +4,7 @@ description: Conduz a fase de revisão e produz o review-report.md para o Gate 3
4
4
  version: core
5
5
  when-to-invoke: ao iniciar a fase review, após todas as tasks de tasks.json estarem com status `done`
6
6
  user-invokable: true
7
- cliVersion: "7.7.1"
7
+ cliVersion: "7.7.3"
8
8
  ---
9
9
 
10
10
  # morph-review
@@ -4,7 +4,7 @@ description: Varre o codebase existente, detecta stack atual, compara com o stac
4
4
  version: core
5
5
  when-to-invoke: ao iniciar uma feature em projeto brownfield (codebase já existente)
6
6
  user-invokable: true
7
- cliVersion: "7.7.1"
7
+ cliVersion: "7.7.3"
8
8
  ---
9
9
 
10
10
  # morph-stack-scan
@@ -4,7 +4,7 @@ description: Localiza e carrega um standard específico do STANDARDS.json sob de
4
4
  version: core
5
5
  when-to-invoke: quando precisar consultar um standard específico durante a implementação
6
6
  user-invokable: true
7
- cliVersion: "7.7.1"
7
+ cliVersion: "7.7.3"
8
8
  ---
9
9
 
10
10
  # morph-standards
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polymorphism-tech/morph-spec",
3
- "version": "7.7.1",
3
+ "version": "7.7.3",
4
4
  "description": "MORPH-SPEC: NLH (Natural Language Harness) for spec-driven development with Claude Code",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -196,7 +196,10 @@ async function doctorFullCommand(frameworkRoot) {
196
196
  let projectTags = [];
197
197
  const cfgPath = join(frameworkRoot, '.morph/config/config.json');
198
198
  if (await pathExists(cfgPath)) {
199
- try { projectTags = getProjectTags(JSON.parse(await fs.readFile(cfgPath, 'utf8'))); } catch { /* no tags */ }
199
+ try {
200
+ const rawCfg = (await fs.readFile(cfgPath, 'utf8')).replace(/^/, '');
201
+ projectTags = getProjectTags(JSON.parse(rawCfg));
202
+ } catch { /* no tags */ }
200
203
  }
201
204
  const drift = checkAgentsDrift(frameworkRoot, join(frameworkRoot, '.morph/framework'), projectTags);
202
205
  check(`Personas installed (${drift.expected - drift.missing.length}/${drift.expected})`,
@@ -410,6 +410,8 @@ export async function updateCommand(options) {
410
410
  if (updateStandards) logger.dim(' ✓ .morph/framework/standards/');
411
411
  logger.dim(' ✓ .morph/framework/hooks/');
412
412
  logger.dim(' ✓ .morph/framework/agents.json');
413
+ logger.dim(' ✓ .morph/framework/MORPH.md + phases.json (NLH charter)');
414
+ logger.dim(' ✓ .morph/framework/schemas/ + rubrics/ + evals/ (bar 9 + LLM rubrics)');
413
415
  if (commandsCopied) logger.dim(' ✓ .claude/commands/');
414
416
 
415
417
  logger.dim(` ✓ .claude/settings.local.json (${hooksResult.installed} hooks installed)`);
@@ -92,7 +92,10 @@ async function installStackAwareAssets(targetPath, log) {
92
92
  let projectTags = [];
93
93
  if (await pathExists(configPath)) {
94
94
  try {
95
- const config = JSON.parse(readFileSync(configPath, 'utf-8'));
95
+ // BOM-tolerant: a config.json hand-edited on Windows (PS 5.1 Out-File)
96
+ // carries a UTF-8 BOM; without stripping it this parse fails silently
97
+ // and only universal personas get installed.
98
+ const config = JSON.parse(readFileSync(configPath, 'utf-8').replace(/^/, ''));
96
99
  projectTags = getProjectTags(config);
97
100
  } catch { /* ignore malformed config */ }
98
101
  }
@@ -1,51 +1,86 @@
1
- // src/utils/banner.js
2
- import chalk from 'chalk';
3
- import { type as osType, release as osRelease } from 'os';
4
-
5
- const BRAND = chalk.hex('#7C3AED');
6
- const DIM = chalk.dim;
7
-
8
- function platformLabel() {
9
- const t = osType();
10
- const r = osRelease().split('.').slice(0, 2).join('.');
11
- if (t === 'Windows_NT') return `Windows ${r}`;
12
- if (t === 'Darwin') return `macOS ${r}`;
13
- return `Linux ${r}`;
14
- }
15
-
16
- export function printInstallBanner({ version, nodeVersion, results = [] }) {
17
- const TITLE = '◆ M O R P H - S P E C';
18
- const pad = ` ${TITLE} `;
19
- const line = '─'.repeat(pad.length);
20
-
21
- const box = [
22
- BRAND(`┌${line}┐`),
23
- BRAND('│') + chalk.bold.white(pad) + BRAND('│'),
24
- BRAND(`└${line}┘`),
25
- ].join('\n');
26
-
27
- const sep = (label) => DIM(` ── ${label} ${'─'.repeat(Math.max(0, 30 - label.length))}`);
28
-
29
- const itemLines = results.map(r =>
30
- r.ok
31
- ? ` ${chalk.green('✔')} ${r.label.padEnd(20)} ${DIM(r.path)}`
32
- : ` ${chalk.red('✖')} ${r.label.padEnd(20)} ${chalk.red(r.error ?? 'failed')}`
33
- );
34
-
35
- console.log([
36
- '',
37
- box,
38
- '',
39
- ` Version ${chalk.white(version)}`,
40
- ` Platform ${chalk.white(platformLabel())}`,
41
- ` Node ${chalk.white(nodeVersion)}`,
42
- '',
43
- sep('Installed'),
44
- ...itemLines,
45
- '',
46
- sep('Next'),
47
- ` ${chalk.cyan('1.')} Open your project in Claude Code`,
48
- ` ${chalk.cyan('2.')} Run ${chalk.bold.white('/morph-init')}`,
49
- '',
50
- ].join('\n'));
51
- }
1
+ // src/utils/banner.js
2
+ import chalk from 'chalk';
3
+ import { type as osType, release as osRelease } from 'os';
4
+
5
+ const BRAND = chalk.hex('#7C3AED');
6
+ const DIM = chalk.dim;
7
+
8
+ /** Purple gradient, one tone per logo line (top → bottom). */
9
+ const GRADIENT = ['#6D28D9', '#7C3AED', '#8B5CF6', '#A78BFA', '#C4B5FD', '#DDD6FE'];
10
+
11
+ const LOGO_LINES = [
12
+ '███╗ ███╗ ██████╗ ██████╗ ██████╗ ██╗ ██╗',
13
+ '████╗ ████║██╔═══██╗██╔══██╗██╔══██╗██║ ██║',
14
+ '██╔████╔██║██║ ██║██████╔╝██████╔╝███████║',
15
+ '██║╚██╔╝██║██║ ██║██╔══██╗██╔═══╝ ██╔══██║',
16
+ '██║ ╚═╝ ██║╚██████╔╝██║ ██║██║ ██║ ██║',
17
+ '╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝',
18
+ ];
19
+
20
+ const LOGO_WIDTH = LOGO_LINES[0].length; // 44
21
+
22
+ /**
23
+ * The morph-spec logo: gradient "MORPH" block art with a S P E C + version
24
+ * rule underneath. Single source for the CLI help banner and the install
25
+ * banner — one drawing, everywhere.
26
+ *
27
+ * @param {string} version - e.g. "7.7.2"
28
+ * @returns {string}
29
+ */
30
+ export function renderLogo(version) {
31
+ const art = LOGO_LINES
32
+ .map((line, i) => ' ' + chalk.hex(GRADIENT[i])(line))
33
+ .join('\n');
34
+
35
+ const label = ` S P E C `;
36
+ const ver = ` v${version} `;
37
+ const plain = `${label}·${ver}`;
38
+ const side = Math.max(2, Math.floor((LOGO_WIDTH - plain.length) / 2));
39
+ const rule =
40
+ ' ' +
41
+ DIM('─'.repeat(side)) +
42
+ BRAND.bold(label) +
43
+ DIM('·') +
44
+ chalk.bold.white(ver) +
45
+ DIM('─'.repeat(Math.max(2, LOGO_WIDTH - side - plain.length)));
46
+
47
+ return `\n${art}\n${rule}\n`;
48
+ }
49
+
50
+ function platformLabel() {
51
+ const t = osType();
52
+ const r = osRelease().split('.').slice(0, 2).join('.');
53
+ if (t === 'Windows_NT') return `Windows ${r}`;
54
+ if (t === 'Darwin') return `macOS ${r}`;
55
+ return `Linux ${r}`;
56
+ }
57
+
58
+ export function printInstallBanner({ version, nodeVersion, results = [] }) {
59
+ const sep = (label) => DIM(` ── ${label} ${'─'.repeat(Math.max(0, 38 - label.length))}`);
60
+
61
+ const itemLines = results.map(r =>
62
+ r.ok
63
+ ? ` ${chalk.green('✔')} ${r.label.padEnd(20)} ${DIM(r.path)}`
64
+ : ` ${chalk.red('✖')} ${r.label.padEnd(20)} ${chalk.red(r.error ?? 'failed')}`
65
+ );
66
+
67
+ console.log([
68
+ renderLogo(version),
69
+ ` ${DIM('Spec-driven development harness for Claude Code')}`,
70
+ ` ${DIM('by Polymorphism Tech')} ${DIM('·')} ${DIM('Node')} ${chalk.white(nodeVersion)} ${DIM('·')} ${chalk.white(platformLabel())}`,
71
+ '',
72
+ sep('Installed'),
73
+ ...itemLines,
74
+ '',
75
+ sep('Next · new project'),
76
+ ` ${chalk.cyan('1.')} ${DIM('cd')} ${chalk.white('your-project')} ${DIM('(git repo)')}`,
77
+ ` ${chalk.cyan('2.')} Run ${chalk.bold.white('morph-spec init')}`,
78
+ ` ${chalk.cyan('3.')} Open it in Claude Code and run ${chalk.bold.white('/morph-init')}`,
79
+ '',
80
+ sep('Next · project already on morph-spec'),
81
+ ` ${chalk.cyan('1.')} Run ${chalk.bold.white('morph-spec update')} ${DIM('— sync charter, rubrics, hooks, personas')}`,
82
+ ` ${chalk.cyan('2.')} Run ${chalk.bold.white('morph-spec doctor')} ${DIM('— validate install + personas drift')}`,
83
+ ` ${chalk.cyan('3.')} ${DIM('Check')} ${chalk.white('.morph/config/config.json → project.stack')} ${DIM('(drives which personas install)')}`,
84
+ '',
85
+ ].join('\n'));
86
+ }
@@ -37,7 +37,12 @@ export async function pathExists(path) {
37
37
  }
38
38
 
39
39
  export async function readJson(path) {
40
- return fs.readJson(path);
40
+ // BOM-tolerant: PowerShell 5.1's `Out-File -Encoding utf8` writes UTF-8
41
+ // WITH BOM, and JSON.parse rejects it. A hand-edited config.json on Windows
42
+ // would then silently fall back to empty project tags (fail-open reads),
43
+ // installing only universal personas. Strip the BOM before parsing.
44
+ const raw = await fs.readFile(path, 'utf-8');
45
+ return JSON.parse(raw.replace(/^/, ''));
41
46
  }
42
47
 
43
48
  export async function writeJson(path, data) {