Skip to content

Commit 97a4f92

Browse files
chore(plugin): bump version 1.103.0 -> 1.103.1
1 parent 1f33272 commit 97a4f92

File tree

3 files changed

+110
-52
lines changed

3 files changed

+110
-52
lines changed

.claude-plugin/marketplace.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
},
77
"metadata": {
88
"description": "H·AI·K·U — universal lifecycle orchestration with hat-based workflows, completion criteria, and automatic context preservation.",
9-
"version": "1.103.0"
9+
"version": "1.103.1"
1010
},
1111
"plugins": [
1212
{

plugin/.claude-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "haiku",
3-
"version": "1.103.0",
3+
"version": "1.103.1",
44
"description": "H·AI·K·U methodology — universal lifecycle orchestration with hat-based workflows, completion criteria, and automatic context preservation.",
55
"author": {
66
"name": "GigSmart",

plugin/bin/haiku

Lines changed: 108 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -64441,7 +64441,7 @@ var MCP_VERSION;
6444164441
var init_version3 = __esm({
6444264442
"src/version.ts"() {
6444364443
"use strict";
64444-
MCP_VERSION = true ? "1.103.0" : "dev";
64444+
MCP_VERSION = true ? "1.103.1" : "dev";
6444564445
}
6444664446
});
6444764447

@@ -75129,18 +75129,6 @@ import {
7512975129
writeFileSync as writeFileSync2
7513075130
} from "node:fs";
7513175131
import { join as join12, resolve as resolve3 } from "node:path";
75132-
function deriveIntentTitle(input) {
75133-
if (!input) return "";
75134-
const collapsed = input.replace(/\s+/g, " ").trim();
75135-
if (!collapsed) return "";
75136-
if (collapsed.length <= INTENT_TITLE_MAX_LENGTH) {
75137-
return collapsed.replace(/\.$/, "");
75138-
}
75139-
const hardLimit = INTENT_TITLE_MAX_LENGTH - 1;
75140-
const truncated = collapsed.slice(0, hardLimit);
75141-
const lastSpace = truncated.lastIndexOf(" ");
75142-
return `${lastSpace > INTENT_TITLE_MAX_LENGTH / 2 ? truncated.slice(0, lastSpace) : truncated}\u2026`;
75143-
}
7514475132
function intentTitleNeedsRepair(title) {
7514575133
if (typeof title !== "string") return true;
7514675134
const trimmed = title.trim();
@@ -75155,38 +75143,23 @@ function applyAutoFixes(intentRoot, slug, issues) {
7515575143
const raw = readFileSync5(intentPath, "utf8");
7515675144
const parsed = (0, import_gray_matter2.default)(raw);
7515775145
const data = parsed.data;
75158-
let body = parsed.content;
75146+
const body = parsed.content;
7515975147
let changed = false;
7516075148
const applied = [];
7516175149
const remaining = [];
7516275150
for (const issue3 of issues) {
7516375151
let fixedHere = false;
7516475152
if (issue3.field === "title" && typeof data.title === "string" && intentTitleNeedsRepair(data.title)) {
75165-
const oldTitle = data.title;
75166-
const newTitle = deriveIntentTitle(oldTitle);
75167-
data.title = newTitle;
75168-
const h1Re = /^#\s+(.+?)\s*$/m;
75169-
const h1Match = body.match(h1Re);
75170-
const oldDescription = oldTitle.replace(/\s+/g, " ").trim();
75171-
if (h1Match && h1Match[1].replace(/\s+/g, " ").trim() === oldDescription) {
75172-
body = body.replace(h1Re, `# ${newTitle}
75173-
75174-
${oldDescription}`);
75175-
} else if (!h1Match) {
75176-
body = `${`# ${newTitle}
75177-
75178-
${oldDescription}
75179-
75180-
${body}`.trimEnd()}
75181-
`;
75182-
}
75183-
applied.push({
75153+
const oldTitle = data.title.replace(/\s+/g, " ").trim();
75154+
const preview = oldTitle.length > 120 ? `${oldTitle.slice(0, 117)}...` : oldTitle;
75155+
remaining.push({
7518475156
intent: slug,
7518575157
field: "title",
75186-
description: `Trimmed title from ${oldTitle.length} chars to ${newTitle.length} chars; full description preserved in body`
75158+
severity: "error",
75159+
message: `Title is ${oldTitle.length} chars \u2014 looks auto-truncated or is a full description, not a title`,
75160+
fix: `Rewrite as a crisp 3\u20138 word summary (\u226480 chars, single line, no trailing period). Preserve the current text as a paragraph in the body under the H1 if it isn't there already. Original: "${preview}"`
7518775161
});
7518875162
fixedHere = true;
75189-
changed = true;
7519075163
}
7519175164
if (issue3.field === "created" && data.created && !data.created_at) {
7519275165
data.created_at = data.created;
@@ -75303,8 +75276,65 @@ ${body}`.trimEnd()}
7530375276
}
7530475277
}
7530575278
}
75306-
const stageRemaining = [];
75279+
const inputsRemaining = [];
75280+
const unitInputsRe = /^stages\/([^/]+)\/units\/([^/]+):inputs$/;
7530775281
for (const issue3 of remaining) {
75282+
const m2 = issue3.field.match(unitInputsRe);
75283+
if (!m2 || !issue3.message.includes("Unit has no `inputs:`") || typeof issue3.fix !== "string") {
75284+
inputsRemaining.push(issue3);
75285+
continue;
75286+
}
75287+
const stageName = m2[1];
75288+
const unitFile = m2[2];
75289+
const unitPath2 = join12(
75290+
intentRoot,
75291+
slug,
75292+
"stages",
75293+
stageName,
75294+
"units",
75295+
unitFile
75296+
);
75297+
if (!existsSync6(unitPath2)) {
75298+
inputsRemaining.push(issue3);
75299+
continue;
75300+
}
75301+
let inputsToWrite = [];
75302+
const upstreamMatch = issue3.fix.match(/upstream paths:\s*(.+?)\s*$/);
75303+
if (upstreamMatch) {
75304+
inputsToWrite = upstreamMatch[1].split(",").map((s) => s.trim()).filter(Boolean);
75305+
} else {
75306+
const fallback = ["intent.md"];
75307+
const knowledgeDir = join12(intentRoot, slug, "knowledge");
75308+
if (existsSync6(knowledgeDir)) {
75309+
for (const f of readdirSync2(knowledgeDir)) {
75310+
if (f.endsWith(".md")) fallback.push(`knowledge/${f}`);
75311+
}
75312+
}
75313+
inputsToWrite = fallback;
75314+
}
75315+
if (inputsToWrite.length === 0) {
75316+
inputsRemaining.push(issue3);
75317+
continue;
75318+
}
75319+
const unitRaw = readFileSync5(unitPath2, "utf8");
75320+
const unitParsed = (0, import_gray_matter2.default)(unitRaw);
75321+
const existing = unitParsed.data.inputs || [];
75322+
if (existing.length > 0) {
75323+
continue;
75324+
}
75325+
unitParsed.data.inputs = inputsToWrite;
75326+
writeFileSync2(
75327+
unitPath2,
75328+
import_gray_matter2.default.stringify(unitParsed.content, unitParsed.data)
75329+
);
75330+
applied.push({
75331+
intent: slug,
75332+
field: issue3.field,
75333+
description: `Linked ${inputsToWrite.length} input(s): ${inputsToWrite.join(", ")}`
75334+
});
75335+
}
75336+
const stageRemaining = [];
75337+
for (const issue3 of inputsRemaining) {
7530875338
let fixedHere = false;
7530975339
if (issue3.field.match(/^stages\/[^/]+\/state\.json$/) && issue3.message.includes("before active_stage")) {
7531075340
const stageMatch = issue3.field.match(/^stages\/([^/]+)\/state\.json$/);
@@ -75413,14 +75443,13 @@ function scanOneIntent(intentsDir, slug, studioMap, searchPaths) {
7541375443
});
7541475444
} else if (typeof repairData.title === "string" && intentTitleNeedsRepair(repairData.title)) {
7541575445
const current = repairData.title;
75416-
const derived = deriveIntentTitle(current);
7541775446
const reason = /\n/.test(current) ? "title contains newlines" : `title is ${current.length} chars (max ${INTENT_TITLE_MAX_LENGTH})`;
7541875447
issues.push({
7541975448
intent: slug,
7542075449
field: "title",
75421-
severity: "warning",
75450+
severity: "error",
7542275451
message: `Title should be a short one-liner \u2014 ${reason}`,
75423-
fix: `Replace \`title\` with: "${derived.replace(/"/g, '\\"')}". Move the full description into the intent body as a paragraph under the H1.`
75452+
fix: "Rewrite `title` as a crisp 3\u20138 word summary (\u226480 chars, single line, no trailing period). Do NOT truncate the current value \u2014 write a deliberate human-readable summary. Preserve the original text as a paragraph in the body under the H1 if it isn't there already."
7542475453
});
7542575454
}
7542675455
if (!repairData.studio) {
@@ -105197,9 +105226,35 @@ ${errorStack}
105197105226
}
105198105227
if (name === "haiku_intent_create") {
105199105228
const description = args2.description;
105229+
const titleInput = args2.title;
105200105230
let slug = args2.slug;
105231+
if (!titleInput || typeof titleInput !== "string") {
105232+
return text(
105233+
JSON.stringify({
105234+
error: "missing_title",
105235+
message: 'haiku_intent_create requires a `title` parameter \u2014 a crisp 3\u20138 word summary (\u226480 chars, single line, no trailing period). Write it deliberately; do NOT pass a truncated description. Example: title: "Add archivable intents".'
105236+
})
105237+
);
105238+
}
105239+
if (/[\r\n]/.test(titleInput)) {
105240+
return text(
105241+
JSON.stringify({
105242+
error: "invalid_title",
105243+
message: "`title` must be a single line \u2014 got newlines. Rewrite as a crisp 3\u20138 word summary (\u226480 chars) and call again."
105244+
})
105245+
);
105246+
}
105247+
const title = titleInput.trim().replace(/\s+/g, " ");
105248+
if (intentTitleNeedsRepair(title)) {
105249+
return text(
105250+
JSON.stringify({
105251+
error: "invalid_title",
105252+
message: `\`title\` must be non-empty and \u226480 chars after trimming. Got ${title.length} chars. Rewrite as a 3\u20138 word summary and call again.`
105253+
})
105254+
);
105255+
}
105201105256
if (!slug) {
105202-
slug = description.toLowerCase().replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 50).replace(/-$/, "");
105257+
slug = title.toLowerCase().replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 50).replace(/-$/, "");
105203105258
}
105204105259
slug = validateIdentifier(slug, "intent slug");
105205105260
const stateFile = args2.state_file;
@@ -105233,9 +105288,7 @@ ${errorStack}
105233105288
const context2 = args2.context;
105234105289
const mode = args2.mode || "continuous";
105235105290
const stagesOverride = args2.stages;
105236-
const title = deriveIntentTitle(description);
105237-
const descriptionBody = description.trim();
105238-
const bodyHasDescription = descriptionBody && descriptionBody !== title;
105291+
const descriptionBody = (description || "").trim();
105239105292
const intentContent = [
105240105293
"---",
105241105294
`title: "${title.replace(/"/g, '\\"')}"`,
@@ -105249,7 +105302,7 @@ ${stagesOverride.map((s) => ` - ${s}`).join("\n")}`] : [],
105249105302
"",
105250105303
`# ${title}`,
105251105304
"",
105252-
...bodyHasDescription ? [descriptionBody, ""] : [],
105305+
...descriptionBody ? [descriptionBody, ""] : [],
105253105306
...context2 ? [context2, ""] : []
105254105307
].join("\n");
105255105308
writeFileSync3(join15(iDir, "intent.md"), intentContent);
@@ -105537,7 +105590,7 @@ ${studioDescriptions}`
105537105590
const raw = readFileSync8(intentFile, "utf8");
105538105591
const { data, body } = parseFrontmatter(raw);
105539105592
const title = data.title || "";
105540-
const description = title || body.replace(/^#\s+.*\n/, "").trim();
105593+
const description = body.replace(/^#\s+.*\n+/, "").trim() || title;
105541105594
if (_elicitInput) {
105542105595
const result = await _elicitInput({
105543105596
message: `Reset intent "${slug}"?
@@ -105589,9 +105642,10 @@ Description: "${description}"`,
105589105642
{
105590105643
action: "intent_reset",
105591105644
slug,
105645+
title,
105592105646
description,
105593105647
context: conversationContext,
105594-
message: `Intent '${slug}' has been reset. Call haiku_intent_create { description: "${description.replace(/"/g, '\\"')}", slug: "${slug}"${conversationContext ? ', context: "<preserved context>"' : ""} } to recreate it.`
105648+
message: `Intent '${slug}' has been reset. Call haiku_intent_create { title: "${title.replace(/"/g, '\\"')}", description: "${description.replace(/"/g, '\\"').replace(/\n/g, "\\n")}", slug: "${slug}"${conversationContext ? ', context: "<preserved context>"' : ""} } to recreate it.`
105595105649
},
105596105650
null,
105597105651
2
@@ -105636,17 +105690,21 @@ var init_orchestrator = __esm({
105636105690
// haiku_gate_approve removed — gates are handled by the FSM (review UI + elicitation fallback)
105637105691
{
105638105692
name: "haiku_intent_create",
105639-
description: "Create a new H\xB7AI\xB7K\xB7U intent. Studio selection happens separately via haiku_select_studio.",
105693+
description: 'Create a new H\xB7AI\xB7K\xB7U intent. Studio selection happens separately via haiku_select_studio. You must provide BOTH a crisp `title` (3\u20138 words, \u226480 chars, single line, no trailing punctuation \u2014 e.g. "Add archivable intents") AND a richer `description` (2\u20135 sentences covering scope, motivation, and constraints). The title is NOT derived from the description \u2014 write it deliberately as a human-readable summary.',
105640105694
inputSchema: {
105641105695
type: "object",
105642105696
properties: {
105697+
title: {
105698+
type: "string",
105699+
description: 'Short human-readable title (3\u20138 words, max 80 chars, single line, no trailing period). Must be a deliberate summary \u2014 NOT the first 80 chars of the description. Good: "Add archivable intents". Bad: "Add archivable intents to H\xB7AI\xB7K\xB7U. Users need a way to soft-hide\u2026".'
105700+
},
105643105701
description: {
105644105702
type: "string",
105645-
description: "What the intent is about"
105703+
description: "Full description of what the intent is about (2\u20135 sentences covering scope, motivation, and constraints). Stored verbatim in the intent body."
105646105704
},
105647105705
slug: {
105648105706
type: "string",
105649-
description: "URL-friendly slug for the intent (auto-generated from description if not provided)"
105707+
description: "URL-friendly slug for the intent (auto-generated from title if not provided)"
105650105708
},
105651105709
context: {
105652105710
type: "string",
@@ -105663,7 +105721,7 @@ var init_orchestrator = __esm({
105663105721
description: "Explicit stage list \u2014 overrides the studio's default stages. Use to run a subset of stages (e.g. just ['development'] for quick tasks)."
105664105722
}
105665105723
},
105666-
required: ["description"]
105724+
required: ["title", "description"]
105667105725
}
105668105726
},
105669105727
{

0 commit comments

Comments
 (0)