@meltstudio/meltctl 5.17.0 → 5.19.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.
Files changed (2) hide show
  1. package/dist/index.js +42 -15
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -7450,7 +7450,7 @@ var CLI_VERSION;
7450
7450
  var init_version = __esm({
7451
7451
  "src/utils/version.ts"() {
7452
7452
  "use strict";
7453
- CLI_VERSION = "5.17.0";
7453
+ CLI_VERSION = "5.19.0";
7454
7454
  }
7455
7455
  });
7456
7456
 
@@ -27403,8 +27403,14 @@ function createPmResource(config2) {
27403
27403
  });
27404
27404
  return unwrap("promote scenario", res);
27405
27405
  },
27406
- async assignFeature(phaseId, featureId) {
27407
- const res = await apiFetch(config2, `/pm/phases/${phaseId}/features`, { method: "POST", body: JSON.stringify({ featureId }) });
27406
+ /**
27407
+ * Add a feature to a phase, optionally authoring the per-phase intent in
27408
+ * the same call (#611). Omitting `init` keeps the historical behaviour —
27409
+ * the membership takes the schema defaults (target `mv`, placeholder
27410
+ * scope) and the PM refines later via {@link updatePhaseFeature}.
27411
+ */
27412
+ async assignFeature(phaseId, featureId, init) {
27413
+ const res = await apiFetch(config2, `/pm/phases/${phaseId}/features`, { method: "POST", body: JSON.stringify({ featureId, ...init }) });
27408
27414
  return unwrap("assign feature", res, 201);
27409
27415
  },
27410
27416
  async unassignFeature(phaseId, featureId) {
@@ -43237,6 +43243,7 @@ function detectAuditType(filename) {
43237
43243
  const lower = filename.toLowerCase();
43238
43244
  if (lower.includes("security-audit")) return "security-audit";
43239
43245
  if (lower.includes("ux-audit")) return "ux-audit";
43246
+ if (lower.includes("cost-audit")) return "cost-audit";
43240
43247
  return "audit";
43241
43248
  }
43242
43249
  async function autoDetectAuditFile() {
@@ -43324,7 +43331,8 @@ async function auditListCommand(options) {
43324
43331
  const typeLabels = {
43325
43332
  audit: "Tech Audit",
43326
43333
  "ux-audit": "UX Audit",
43327
- "security-audit": "Security"
43334
+ "security-audit": "Security",
43335
+ "cost-audit": "Cost Audit"
43328
43336
  };
43329
43337
  if (options.latest) {
43330
43338
  console.log(source_default.bold(`
@@ -43370,7 +43378,7 @@ function printLatestAuditRow(r, typeLabels) {
43370
43378
  });
43371
43379
  const repo = r.repository ?? r.project;
43372
43380
  const label = typeLabels[r.type] ?? r.type;
43373
- const typeColor = r.type === "ux-audit" ? source_default.yellow : r.type === "security-audit" ? source_default.red : source_default.magenta;
43381
+ const typeColor = r.type === "ux-audit" ? source_default.yellow : r.type === "security-audit" ? source_default.red : r.type === "cost-audit" ? source_default.cyan : source_default.magenta;
43374
43382
  const ageText = daysAgo === 0 ? "today" : `${daysAgo}d ago`;
43375
43383
  const isSecurityAudit = r.type === "security-audit";
43376
43384
  const ageColor = isSecurityAudit ? daysAgo <= 30 ? source_default.green : daysAgo <= 90 ? source_default.yellow : source_default.red : daysAgo <= 7 ? source_default.green : daysAgo <= 30 ? source_default.yellow : source_default.red;
@@ -43388,7 +43396,7 @@ function printAuditRow(r, typeLabels) {
43388
43396
  });
43389
43397
  const repo = r.repository ?? r.project;
43390
43398
  const label = typeLabels[r.type] ?? r.type;
43391
- const typeColor = r.type === "ux-audit" ? source_default.yellow : r.type === "security-audit" ? source_default.red : source_default.magenta;
43399
+ const typeColor = r.type === "ux-audit" ? source_default.yellow : r.type === "security-audit" ? source_default.red : r.type === "cost-audit" ? source_default.cyan : source_default.magenta;
43392
43400
  console.log(
43393
43401
  ` ${source_default.dim(r.id.slice(0, 8).padEnd(10))} ${typeColor(label.padEnd(12))} ${source_default.white(repo.padEnd(40))} ${source_default.dim(r.author.padEnd(30))} ${source_default.dim(date5)}`
43394
43402
  );
@@ -43413,7 +43421,8 @@ async function auditViewCommand(id, options) {
43413
43421
  const typeLabels = {
43414
43422
  audit: "Tech Audit",
43415
43423
  "ux-audit": "UX Audit",
43416
- "security-audit": "Security Audit"
43424
+ "security-audit": "Security Audit",
43425
+ "cost-audit": "Cost Audit"
43417
43426
  };
43418
43427
  const label = typeLabels[audit2.type] ?? audit2.type;
43419
43428
  const date5 = new Date(audit2.createdAt).toLocaleDateString("en-US", {
@@ -54052,8 +54061,14 @@ function createPmResource2(config2) {
54052
54061
  });
54053
54062
  return unwrap2("promote scenario", res);
54054
54063
  },
54055
- async assignFeature(phaseId, featureId) {
54056
- const res = await apiFetch2(config2, `/pm/phases/${phaseId}/features`, { method: "POST", body: JSON.stringify({ featureId }) });
54064
+ /**
54065
+ * Add a feature to a phase, optionally authoring the per-phase intent in
54066
+ * the same call (#611). Omitting `init` keeps the historical behaviour —
54067
+ * the membership takes the schema defaults (target `mv`, placeholder
54068
+ * scope) and the PM refines later via {@link updatePhaseFeature}.
54069
+ */
54070
+ async assignFeature(phaseId, featureId, init) {
54071
+ const res = await apiFetch2(config2, `/pm/phases/${phaseId}/features`, { method: "POST", body: JSON.stringify({ featureId, ...init }) });
54057
54072
  return unwrap2("assign feature", res, 201);
54058
54073
  },
54059
54074
  async unassignFeature(phaseId, featureId) {
@@ -55355,7 +55370,8 @@ async function deletePhase(client, input) {
55355
55370
  });
55356
55371
  }
55357
55372
  async function assignFeatureToPhase(client, input) {
55358
- return safe(() => client.pm.assignFeature(input.phaseId, input.featureId));
55373
+ const { phaseId, featureId, ...init } = input;
55374
+ return safe(() => client.pm.assignFeature(phaseId, featureId, init));
55359
55375
  }
55360
55376
  async function unassignFeatureFromPhase(client, input) {
55361
55377
  return safe(() => client.pm.unassignFeature(input.phaseId, input.featureId));
@@ -55471,10 +55487,19 @@ Mandatory caller behavior: present the phase's name + id + active/closed status
55471
55487
  "assign_feature_to_phase",
55472
55488
  {
55473
55489
  title: "Assign feature to phase",
55474
- description: "Adds an existing feature to an existing phase. Captures the feature's current_stage as a stage_snapshot at join time \u2014 closed phases keep the snapshot they ended at, even if the feature later progresses. The feature and phase must belong to the same project. Returns the membership row.",
55490
+ description: "Adds an existing feature to an existing phase, and is where the per-phase INTENT should be authored. A feature can sit in several phases, so what matters is which SLICE of it this phase builds \u2014 set targetStage and phaseDescription here rather than leaving the defaults for a follow-up update_phase_feature call. Left unset they fall back to target 'mv' and the placeholder 'PM to refine', which renders on the roadmap as an unscoped row the PM has to come back and fix. Captures the feature's current_stage as a stage_snapshot at join time \u2014 closed phases keep the snapshot they ended at, even if the feature later progresses. The feature and phase must belong to the same project. Returns the membership row.",
55475
55491
  inputSchema: {
55476
55492
  phaseId: external_exports.string().uuid().describe("Phase id from list_phases or create_phase."),
55477
- featureId: external_exports.string().uuid().describe("Feature id from list_features or create_feature.")
55493
+ featureId: external_exports.string().uuid().describe("Feature id from list_features or create_feature."),
55494
+ targetStage: external_exports.enum(STAGE_VALUES).optional().describe(
55495
+ "Per-phase target stage \u2014 what THIS phase brings the feature to, which is usually not its eventual target (e.g. 'pt' in an early phase, 'mv' in a later one). 7-stage enum (idea | poc | pt | mv | mk | ma | mbi). Defaults to 'mv' when omitted."
55496
+ ),
55497
+ phaseDescription: external_exports.string().min(1).optional().describe(
55498
+ "Per-phase scope: which part of the feature this phase builds, in PM voice \u2014 e.g. 'Core: email/password auth' in one phase and 'Extended: Google + Apple SSO, magic-link spike' in the next. Shown on the roadmap board and the client portal under the feature name, so write it for the client to read. Defaults to the 'PM to refine' placeholder when omitted."
55499
+ ),
55500
+ force: external_exports.boolean().optional().describe(
55501
+ "Required (true) only when setting targetStage on a membership in a LOCKED phase (state active/proposed) \u2014 that moves every downstream planning baseline. Plain assigns never need it."
55502
+ )
55478
55503
  }
55479
55504
  },
55480
55505
  withClientArgs(getClient2, assignFeatureToPhase)
@@ -55495,7 +55520,7 @@ Mandatory caller behavior: present the phase's name + id + active/closed status
55495
55520
  "update_phase_feature",
55496
55521
  {
55497
55522
  title: "Update phase-feature membership",
55498
- description: "Edits the per-phase intent for a feature in a phase: targetStage (what this phase aims to bring the feature to), phaseDescription (PM-authored note about what this jump means in this phase), linearEpicUrl (per-phase Linear epic), status (per-phase status \u2014 a feature can be done in one phase and in_progress in another). The same feature can have different per-phase values across phases. Use after assign_feature_to_phase when the PM wants per-membership values different from the feature-row defaults. The PM should review the proposed change before this is called \u2014 this is a write operation. Pass linearEpicUrl=null to clear the per-phase Linear link. Editing a membership in a LOCKED phase (state active/proposed) requires force=true.",
55523
+ description: "Edits the per-phase intent for a feature in a phase: targetStage (what this phase aims to bring the feature to), phaseDescription (PM-authored note about what this jump means in this phase), linearEpicUrl (per-phase Linear epic), status (per-phase status \u2014 a feature can be done in one phase and in_progress in another). The same feature can have different per-phase values across phases. Use to REVISE a membership \u2014 the initial targetStage and phaseDescription are better set on assign_feature_to_phase, in one call. The PM should review the proposed change before this is called \u2014 this is a write operation. Pass linearEpicUrl=null to clear the per-phase Linear link. Editing a membership in a LOCKED phase (state active/proposed) requires force=true.",
55499
55524
  inputSchema: {
55500
55525
  phaseId: external_exports.string().uuid().describe("Phase id from list_phases."),
55501
55526
  featureId: external_exports.string().uuid().describe("Feature id of the membership to edit."),
@@ -55503,7 +55528,7 @@ Mandatory caller behavior: present the phase's name + id + active/closed status
55503
55528
  "Per-phase target stage. What this phase aims to bring the feature to \u2014 e.g. 'pt' for prototype in an early phase, 'mv' for MVP in a later one. 7-stage enum (idea | poc | pt | mv | mk | ma | mbi)."
55504
55529
  ),
55505
55530
  phaseDescription: external_exports.string().min(1).optional().describe(
55506
- "One-line PM-voice note: what should this feature be doing once this phase's jump lands. New memberships default to 'PM to refine'."
55531
+ "Per-phase scope in PM voice: which part of the feature this phase builds / what it should be doing once this phase's jump lands. Shown on the roadmap board and the client portal under the feature name. New memberships default to the 'PM to refine' placeholder."
55507
55532
  ),
55508
55533
  linearEpicUrl: external_exports.string().url().nullable().optional().describe(
55509
55534
  "Per-phase Linear epic URL. Each phase's work on a feature can be its own epic. Pass null to clear."
@@ -56053,6 +56078,7 @@ ${source_default.dim(" /melt-dev-debug Systematically investigate an
56053
56078
  ${source_default.dim(" /melt-dev-tech-audit Run a project compliance audit")}
56054
56079
  ${source_default.dim(" /melt-dev-ux-audit Review UI against usability heuristics")}
56055
56080
  ${source_default.dim(" /melt-dev-security-audit Run a security posture audit across the platform")}
56081
+ ${source_default.dim(" /melt-dev-cost-audit Run an AWS cost optimization audit")}
56056
56082
  ${source_default.dim(" /melt-dev-help Answer questions about the development playbook")}
56057
56083
 
56058
56084
  ${source_default.bold("Setup")}
@@ -56132,12 +56158,13 @@ ${source_default.dim("Related skills (run in your AI coding tool):")}
56132
56158
  ${source_default.dim(" /melt-dev-tech-audit Run a project compliance audit")}
56133
56159
  ${source_default.dim(" /melt-dev-ux-audit Review UI against usability heuristics")}
56134
56160
  ${source_default.dim(" /melt-dev-security-audit Run a security posture audit across the platform")}
56161
+ ${source_default.dim(" /melt-dev-cost-audit Run an AWS cost optimization audit")}
56135
56162
  `
56136
56163
  );
56137
56164
  audit.command("submit").description("submit an audit report from a markdown file").argument("[file]", "path to the audit file (auto-detects from .audits/ if omitted)").action(async (file2) => {
56138
56165
  await auditSubmitCommand(file2);
56139
56166
  });
56140
- audit.command("list").description("list submitted audits (Team Managers only)").option("--type <type>", "filter by type (audit, ux-audit, security-audit)").option("--repository <repo>", "filter by repository (owner/repo)").option("--latest", "show only the latest audit per project and type").option("--limit <n>", "max results (default 50, max 200)").action(async (options) => {
56167
+ audit.command("list").description("list submitted audits (Team Managers only)").option("--type <type>", "filter by type (audit, ux-audit, security-audit, cost-audit)").option("--repository <repo>", "filter by repository (owner/repo)").option("--latest", "show only the latest audit per project and type").option("--limit <n>", "max results (default 50, max 200)").action(async (options) => {
56141
56168
  await auditListCommand(options);
56142
56169
  });
56143
56170
  audit.command("view").description("view a submitted audit by ID (Team Managers only)").argument("<id>", "audit ID").option("-o, --output <file>", "save to file instead of printing").action(async (id, options) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meltstudio/meltctl",
3
- "version": "5.17.0",
3
+ "version": "5.19.0",
4
4
  "description": "AI-first development tooling for Melt teams — companion CLI for the org-distributed Claude Code dev skills",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",