harnessed 4.0.0 → 4.1.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/dist/cli.mjs CHANGED
@@ -1271,7 +1271,7 @@ var init_auto_install = __esm({
1271
1271
 
1272
1272
  // package.json
1273
1273
  var package_default = {
1274
- version: "4.0.0"};
1274
+ version: "4.1.0"};
1275
1275
 
1276
1276
  // src/manifest/errors.ts
1277
1277
  function instancePathToKeyPath(instancePath) {
@@ -2583,6 +2583,7 @@ function getPackageRoot() {
2583
2583
 
2584
2584
  // src/cli/gates.ts
2585
2585
  var VALID_MASTERS = /* @__PURE__ */ new Set(["auto", "discuss", "plan", "task", "verify"]);
2586
+ var STAGE_MASTERS = /* @__PURE__ */ new Set(["discuss", "plan", "task", "verify"]);
2586
2587
  var PARALLELISM_GATE = "judgments.parallelism-gate.agent-teams-upgrade.fires";
2587
2588
  function defaultContext(task, stage) {
2588
2589
  return {
@@ -2733,6 +2734,9 @@ function fireEntry(clause) {
2733
2734
  if (clause.order !== void 0) entry.order = clause.order;
2734
2735
  if (clause.mode !== void 0) entry.mode = clause.mode;
2735
2736
  if (clause.gate !== void 0) entry.gate = clause.gate;
2737
+ if (STAGE_MASTERS.has(clause.sub)) {
2738
+ entry.is_master = true;
2739
+ }
2736
2740
  return entry;
2737
2741
  }
2738
2742
  var DURATION_RE = /^(\d+)([dhmw])$/;
@@ -4397,6 +4401,7 @@ var INTERACTIVE_COMMANDS = /* @__PURE__ */ new Set([
4397
4401
  ]);
4398
4402
  var ORCHESTRATOR_COMMANDS = /* @__PURE__ */ new Set(["auto", "plan", "task", "verify"]);
4399
4403
  var MARKER = `<!-- harnessed-generated:v3.4.4 -->`;
4404
+ var LANG_DIRECTIVE = `> **Language**: respond to the user in the language set by \`env.HARNESSED_USER_LANG\` (e.g. \`zh-Hans\` \u2192 \u7B80\u4F53\u4E2D\u6587, \`en\` \u2192 English). If unset, mirror the user's input language. Keep code / commands / identifiers / error messages / URLs verbatim.`;
4400
4405
  function spawnLoopSteps(indent) {
4401
4406
  const i = indent;
4402
4407
  return [
@@ -4414,6 +4419,8 @@ function buildInteractiveBody(name, prompt) {
4414
4419
  ``,
4415
4420
  prompt.description,
4416
4421
  ``,
4422
+ LANG_DIRECTIVE,
4423
+ ``,
4417
4424
  `## How to run (interactive \u2014 in THIS session)`,
4418
4425
  ``,
4419
4426
  `Clarification requires real user dialogue. Spawned subagents cannot ask the user questions, so run this stage directly in this session \u2014 do NOT spawn it.`,
@@ -4447,6 +4454,8 @@ function buildOrchestratorBody(name, prompt) {
4447
4454
  ``,
4448
4455
  prompt.description,
4449
4456
  ``,
4457
+ LANG_DIRECTIVE,
4458
+ ``,
4450
4459
  `## How to run (orchestrator \u2014 clarify in THIS session, then drive native subagent spawns)`,
4451
4460
  ``,
4452
4461
  `harnessed is the orchestration brain: \`harnessed gates\` tells you which subs fire, \`harnessed prompt\` gives you each sub's spawn-ready prompt. YOU (the main session) do the spawning with CC-native Task / Agent tools \u2014 keeping the session responsive, enabling Agent Teams, and letting clarification round-trips reach the user.`,
@@ -4455,7 +4464,9 @@ function buildOrchestratorBody(name, prompt) {
4455
4464
  `2. Bash: \`harnessed gates ${name} --task "<locked spec>" --skip-sub clarify\` \u2192 parse the JSON \`{fire: [{sub, order, mode}], skip, parallelism: {escalate_to_teams}}\`.`,
4456
4465
  `3. If \`parallelism.escalate_to_teams === true\`: this stage needs multiple subagents to coordinate (SendMessage / shared contract). Read \`~/.claude/rules/agent-teams.md\`, then \`TeamCreate\` \u2192 \`Agent(name, team_name, ...)\` per fired sub (prompt from \`harnessed prompt <sub>\`) \u2192 coordinate via SendMessage \u2192 \`SendMessage shutdown_request\` + \`TeamDelete\`.`,
4457
4466
  `4. Otherwise, for each fired sub in \`order\` (serial subs sequentially, parallel subs concurrently via parallel Task calls):`,
4458
- ...spawnLoopSteps(" "),
4467
+ ` - **If the fired entry has \`is_master: true\`** (it is itself a stage master, e.g. \`/auto\` firing \`plan\`/\`task\`/\`verify\`): do NOT prompt+spawn it directly \u2014 that would yield a vague dispatcher. RECURSE: run that master's orchestration \u2014 \`harnessed gates <sub> --task "<spec>" --skip-sub clarify\` \u2192 repeat this whole step-4 loop for ITS fired subs. Only leaf subs (no \`is_master\`) reach the spawn loop below.`,
4468
+ ` - **Else (leaf sub)** \u2014 spawn it:`,
4469
+ ...spawnLoopSteps(" "),
4459
4470
  `5. Report a per-sub fired/skipped summary to the user. ${name === "auto" ? "Then run the `retro` stage to capture lessons." : ""}`,
4460
4471
  ``,
4461
4472
  `Do NOT pipe to \`harnessed run ${name}\` \u2014 that is the CI/headless path (SDK spawn, blocks the session, no Agent Teams, no clarification round-trip).`,
@@ -4475,6 +4486,8 @@ function buildExecutionBody(name, prompt) {
4475
4486
  ``,
4476
4487
  prompt.description,
4477
4488
  ``,
4489
+ LANG_DIRECTIVE,
4490
+ ``,
4478
4491
  `## How to run (execution \u2014 native subagent spawn)`,
4479
4492
  ``,
4480
4493
  `harnessed gives you the spawn-ready prompt; YOU spawn the subagent with a CC-native Task / Agent tool (keeps the session responsive + lets clarification round-trips reach the user).`,
@@ -5699,78 +5712,6 @@ async function runWorkflow(yamlPath, vars, opts = {}) {
5699
5712
  ...skippedPhases.length > 0 ? { skippedPhases } : {}
5700
5713
  };
5701
5714
  }
5702
-
5703
- // src/cli/prompt.ts
5704
- var DEFAULT_MAX_ITERATIONS = 20;
5705
- var DEFAULT_MODEL = "sonnet";
5706
- var DEFAULT_SPECIALIST = "Implementation Engineer";
5707
- var PROTOCOLS = `
5708
- ## Completion protocol
5709
- When done emit: <promise>COMPLETE</promise>
5710
-
5711
- ## Clarification protocol
5712
- If you hit a gray area you cannot decide (\u22652 reasonable options, missing key context, ambiguous requirement), do NOT self-decide. Emit:
5713
- STATUS: NEEDS_CLARIFICATION
5714
- 1. <question>
5715
- 2. <question>
5716
- The main session will relay these to the user and re-spawn you with answers.
5717
- `;
5718
- async function resolveMaxIterations2(sub, packageRoot) {
5719
- try {
5720
- const path = resolve(packageRoot, "workflows", "defaults.yaml");
5721
- const raw = await readFile(path, "utf8");
5722
- const doc = parse(raw);
5723
- const entry = doc?.ralph_max_iterations?.[sub];
5724
- if (typeof entry === "number" && Number.isFinite(entry)) {
5725
- return entry;
5726
- }
5727
- if (entry && typeof entry === "object") {
5728
- for (const v of Object.values(entry)) {
5729
- if (typeof v === "number" && Number.isFinite(v)) {
5730
- return v;
5731
- }
5732
- }
5733
- }
5734
- return DEFAULT_MAX_ITERATIONS;
5735
- } catch {
5736
- return DEFAULT_MAX_ITERATIONS;
5737
- }
5738
- }
5739
- function registerPrompt(program2) {
5740
- program2.command("prompt").description(
5741
- "Print a spawn-ready prompt for a sub-workflow (role + checklist + protocols, no spawn)."
5742
- ).argument("<sub>", "sub-workflow name (e.g. task-code, verify-paranoid)").option("--task <text>", "task description prepended as a ## Task section").option("--json", "emit JSON {prompt, max_iterations, model, specialist} instead of text").action(async (sub, raw) => {
5743
- const packageRoot = getPackageRoot();
5744
- const workflowsDir = resolve(packageRoot, "workflows");
5745
- const rolePrompts = await loadRolePrompts(workflowsDir);
5746
- const def = buildAgentDef(sub, rolePrompts, void 0, void 0, void 0);
5747
- const body = def.prompt;
5748
- const taskSection = typeof raw.task === "string" && raw.task.length > 0 ? `## Task
5749
- ${raw.task}
5750
-
5751
- ` : "";
5752
- const fullPrompt = `${taskSection}${body}
5753
- ${PROTOCOLS}`;
5754
- if (raw.json) {
5755
- const maxIterations = await resolveMaxIterations2(sub, packageRoot);
5756
- const rp = rolePrompts[sub];
5757
- const model = def.model ?? DEFAULT_MODEL;
5758
- const specialist = rp?.specialist ?? DEFAULT_SPECIALIST;
5759
- console.log(
5760
- JSON.stringify({
5761
- prompt: fullPrompt,
5762
- max_iterations: maxIterations,
5763
- model,
5764
- specialist
5765
- })
5766
- );
5767
- process.exit(0);
5768
- return;
5769
- }
5770
- console.log(fullPrompt);
5771
- process.exit(0);
5772
- });
5773
- }
5774
5715
  async function loadUserOverrides(packageRoot) {
5775
5716
  const yamlPath = resolve(packageRoot, "workflows", "judgments", "user-overrides.yaml");
5776
5717
  let raw;
@@ -6037,7 +5978,122 @@ async function getNextHint(workflowName) {
6037
5978
  return null;
6038
5979
  }
6039
5980
 
6040
- // src/cli/research.ts
5981
+ // src/cli/prompt.ts
5982
+ var DEFAULT_MAX_ITERATIONS = 20;
5983
+ var DEFAULT_MODEL = "sonnet";
5984
+ var DEFAULT_SPECIALIST = "Implementation Engineer";
5985
+ var LANG_NAMES = {
5986
+ en: "English",
5987
+ "zh-Hans": "\u7B80\u4F53\u4E2D\u6587 (Simplified Chinese)",
5988
+ "zh-CN": "\u7B80\u4F53\u4E2D\u6587 (Simplified Chinese)",
5989
+ "zh-Hant": "\u7E41\u9AD4\u4E2D\u6587 (Traditional Chinese)",
5990
+ "zh-TW": "\u7E41\u9AD4\u4E2D\u6587 (Traditional Chinese)"
5991
+ };
5992
+ async function buildToolsSection(sub, packageRoot) {
5993
+ try {
5994
+ const workflowsDir = resolve(packageRoot, "workflows");
5995
+ const subYaml = await resolveWorkflowYaml(sub, workflowsDir);
5996
+ if (!subYaml) return "";
5997
+ const wfRaw = await readFile(subYaml, "utf8");
5998
+ const wf = parse(wfRaw);
5999
+ const tools = Array.isArray(wf?.tools_available) ? wf.tools_available : [];
6000
+ if (tools.length === 0) return "";
6001
+ const capRaw = await readFile(resolve(workflowsDir, "capabilities.yaml"), "utf8");
6002
+ const capDoc = parse(capRaw);
6003
+ const caps = capDoc?.capabilities ?? {};
6004
+ const lines = [];
6005
+ for (const tool of tools) {
6006
+ const cmd = caps[tool]?.cmd;
6007
+ const impl = caps[tool]?.impl;
6008
+ lines.push(cmd ? `- Invoke \`${cmd}\` (${tool}${impl ? `, ${impl}` : ""})` : `- ${tool}`);
6009
+ }
6010
+ if (lines.length === 0) return "";
6011
+ return `
6012
+ ## Tools \u2014 invoke these (not optional)
6013
+ This workflow's SoT declares the following upstream tools. Actually invoke them as part of your work \u2014 do NOT improvise a lightweight substitute. Persist artifacts in the upstream's native format (e.g. planning-with-files \u2192 \`task_plan.md\` / \`progress.md\` / \`findings.md\`; GSD \u2192 \`PLAN.md\` / \`STATE.md\`).
6014
+ ${lines.join("\n")}
6015
+ `;
6016
+ } catch {
6017
+ return "";
6018
+ }
6019
+ }
6020
+ function buildLanguageSection() {
6021
+ const code = process.env.HARNESSED_USER_LANG;
6022
+ if (!code) return "";
6023
+ const name = LANG_NAMES[code] ?? code;
6024
+ return `
6025
+ ## Language
6026
+ Respond in ${name}. Keep code, commands, file/identifier/API names, error messages, stack traces, URLs, commit hashes, and version numbers in their original form (do not translate or transliterate them).
6027
+ `;
6028
+ }
6029
+ var PROTOCOLS = `
6030
+ ## Completion protocol
6031
+ When done emit: <promise>COMPLETE</promise>
6032
+
6033
+ ## Clarification protocol
6034
+ If you hit a gray area you cannot decide (\u22652 reasonable options, missing key context, ambiguous requirement), do NOT self-decide. Emit:
6035
+ STATUS: NEEDS_CLARIFICATION
6036
+ 1. <question>
6037
+ 2. <question>
6038
+ The main session will relay these to the user and re-spawn you with answers.
6039
+ `;
6040
+ async function resolveMaxIterations2(sub, packageRoot) {
6041
+ try {
6042
+ const path = resolve(packageRoot, "workflows", "defaults.yaml");
6043
+ const raw = await readFile(path, "utf8");
6044
+ const doc = parse(raw);
6045
+ const entry = doc?.ralph_max_iterations?.[sub];
6046
+ if (typeof entry === "number" && Number.isFinite(entry)) {
6047
+ return entry;
6048
+ }
6049
+ if (entry && typeof entry === "object") {
6050
+ for (const v of Object.values(entry)) {
6051
+ if (typeof v === "number" && Number.isFinite(v)) {
6052
+ return v;
6053
+ }
6054
+ }
6055
+ }
6056
+ return DEFAULT_MAX_ITERATIONS;
6057
+ } catch {
6058
+ return DEFAULT_MAX_ITERATIONS;
6059
+ }
6060
+ }
6061
+ function registerPrompt(program2) {
6062
+ program2.command("prompt").description(
6063
+ "Print a spawn-ready prompt for a sub-workflow (role + checklist + protocols, no spawn)."
6064
+ ).argument("<sub>", "sub-workflow name (e.g. task-code, verify-paranoid)").option("--task <text>", "task description prepended as a ## Task section").option("--json", "emit JSON {prompt, max_iterations, model, specialist} instead of text").action(async (sub, raw) => {
6065
+ const packageRoot = getPackageRoot();
6066
+ const workflowsDir = resolve(packageRoot, "workflows");
6067
+ const rolePrompts = await loadRolePrompts(workflowsDir);
6068
+ const def = buildAgentDef(sub, rolePrompts, void 0, void 0, void 0);
6069
+ const body = def.prompt;
6070
+ const taskSection = typeof raw.task === "string" && raw.task.length > 0 ? `## Task
6071
+ ${raw.task}
6072
+
6073
+ ` : "";
6074
+ const toolsSection = await buildToolsSection(sub, packageRoot);
6075
+ const fullPrompt = `${taskSection}${body}
6076
+ ${toolsSection}${PROTOCOLS}${buildLanguageSection()}`;
6077
+ if (raw.json) {
6078
+ const maxIterations = await resolveMaxIterations2(sub, packageRoot);
6079
+ const rp = rolePrompts[sub];
6080
+ const model = def.model ?? DEFAULT_MODEL;
6081
+ const specialist = rp?.specialist ?? DEFAULT_SPECIALIST;
6082
+ console.log(
6083
+ JSON.stringify({
6084
+ prompt: fullPrompt,
6085
+ max_iterations: maxIterations,
6086
+ model,
6087
+ specialist
6088
+ })
6089
+ );
6090
+ process.exit(0);
6091
+ return;
6092
+ }
6093
+ console.log(fullPrompt);
6094
+ process.exit(0);
6095
+ });
6096
+ }
6041
6097
  var PACKAGE_ROOT2 = getPackageRoot();
6042
6098
  var WORKFLOWS_DIR2 = join(PACKAGE_ROOT2, "workflows");
6043
6099
  function registerResearch(program2) {