harnessed 4.0.1 → 4.1.1

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.1"};
1274
+ version: "4.1.1"};
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])$/;
@@ -4460,7 +4464,9 @@ function buildOrchestratorBody(name, prompt) {
4460
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}}\`.`,
4461
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\`.`,
4462
4466
  `4. Otherwise, for each fired sub in \`order\` (serial subs sequentially, parallel subs concurrently via parallel Task calls):`,
4463
- ...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(" "),
4464
4470
  `5. Report a per-sub fired/skipped summary to the user. ${name === "auto" ? "Then run the `retro` stage to capture lessons." : ""}`,
4465
4471
  ``,
4466
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).`,
@@ -5706,94 +5712,6 @@ async function runWorkflow(yamlPath, vars, opts = {}) {
5706
5712
  ...skippedPhases.length > 0 ? { skippedPhases } : {}
5707
5713
  };
5708
5714
  }
5709
-
5710
- // src/cli/prompt.ts
5711
- var DEFAULT_MAX_ITERATIONS = 20;
5712
- var DEFAULT_MODEL = "sonnet";
5713
- var DEFAULT_SPECIALIST = "Implementation Engineer";
5714
- var LANG_NAMES = {
5715
- en: "English",
5716
- "zh-Hans": "\u7B80\u4F53\u4E2D\u6587 (Simplified Chinese)",
5717
- "zh-CN": "\u7B80\u4F53\u4E2D\u6587 (Simplified Chinese)",
5718
- "zh-Hant": "\u7E41\u9AD4\u4E2D\u6587 (Traditional Chinese)",
5719
- "zh-TW": "\u7E41\u9AD4\u4E2D\u6587 (Traditional Chinese)"
5720
- };
5721
- function buildLanguageSection() {
5722
- const code = process.env.HARNESSED_USER_LANG;
5723
- if (!code) return "";
5724
- const name = LANG_NAMES[code] ?? code;
5725
- return `
5726
- ## Language
5727
- 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).
5728
- `;
5729
- }
5730
- var PROTOCOLS = `
5731
- ## Completion protocol
5732
- When done emit: <promise>COMPLETE</promise>
5733
-
5734
- ## Clarification protocol
5735
- If you hit a gray area you cannot decide (\u22652 reasonable options, missing key context, ambiguous requirement), do NOT self-decide. Emit:
5736
- STATUS: NEEDS_CLARIFICATION
5737
- 1. <question>
5738
- 2. <question>
5739
- The main session will relay these to the user and re-spawn you with answers.
5740
- `;
5741
- async function resolveMaxIterations2(sub, packageRoot) {
5742
- try {
5743
- const path = resolve(packageRoot, "workflows", "defaults.yaml");
5744
- const raw = await readFile(path, "utf8");
5745
- const doc = parse(raw);
5746
- const entry = doc?.ralph_max_iterations?.[sub];
5747
- if (typeof entry === "number" && Number.isFinite(entry)) {
5748
- return entry;
5749
- }
5750
- if (entry && typeof entry === "object") {
5751
- for (const v of Object.values(entry)) {
5752
- if (typeof v === "number" && Number.isFinite(v)) {
5753
- return v;
5754
- }
5755
- }
5756
- }
5757
- return DEFAULT_MAX_ITERATIONS;
5758
- } catch {
5759
- return DEFAULT_MAX_ITERATIONS;
5760
- }
5761
- }
5762
- function registerPrompt(program2) {
5763
- program2.command("prompt").description(
5764
- "Print a spawn-ready prompt for a sub-workflow (role + checklist + protocols, no spawn)."
5765
- ).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) => {
5766
- const packageRoot = getPackageRoot();
5767
- const workflowsDir = resolve(packageRoot, "workflows");
5768
- const rolePrompts = await loadRolePrompts(workflowsDir);
5769
- const def = buildAgentDef(sub, rolePrompts, void 0, void 0, void 0);
5770
- const body = def.prompt;
5771
- const taskSection = typeof raw.task === "string" && raw.task.length > 0 ? `## Task
5772
- ${raw.task}
5773
-
5774
- ` : "";
5775
- const fullPrompt = `${taskSection}${body}
5776
- ${PROTOCOLS}${buildLanguageSection()}`;
5777
- if (raw.json) {
5778
- const maxIterations = await resolveMaxIterations2(sub, packageRoot);
5779
- const rp = rolePrompts[sub];
5780
- const model = def.model ?? DEFAULT_MODEL;
5781
- const specialist = rp?.specialist ?? DEFAULT_SPECIALIST;
5782
- console.log(
5783
- JSON.stringify({
5784
- prompt: fullPrompt,
5785
- max_iterations: maxIterations,
5786
- model,
5787
- specialist
5788
- })
5789
- );
5790
- process.exit(0);
5791
- return;
5792
- }
5793
- console.log(fullPrompt);
5794
- process.exit(0);
5795
- });
5796
- }
5797
5715
  async function loadUserOverrides(packageRoot) {
5798
5716
  const yamlPath = resolve(packageRoot, "workflows", "judgments", "user-overrides.yaml");
5799
5717
  let raw;
@@ -6060,7 +5978,155 @@ async function getNextHint(workflowName) {
6060
5978
  return null;
6061
5979
  }
6062
5980
 
6063
- // 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
+ async function buildDisciplinesSection(sub, packageRoot) {
6021
+ try {
6022
+ const workflowsDir = resolve(packageRoot, "workflows");
6023
+ const subYaml = await resolveWorkflowYaml(sub, workflowsDir);
6024
+ if (!subYaml) return "";
6025
+ const wfRaw = await readFile(subYaml, "utf8");
6026
+ const wf = parse(wfRaw);
6027
+ const applied = Array.isArray(wf?.disciplines_applied) ? wf.disciplines_applied : [];
6028
+ const names = applied.filter((d) => d !== "language");
6029
+ if (names.length === 0) return "";
6030
+ const blocks = [];
6031
+ for (const name of names) {
6032
+ try {
6033
+ const dRaw = await readFile(resolve(workflowsDir, "disciplines", `${name}.yaml`), "utf8");
6034
+ const dDoc = parse(dRaw);
6035
+ const rules = Array.isArray(dDoc?.rules) ? dDoc.rules : [];
6036
+ const descs = rules.map((r) => typeof r.description === "string" ? r.description.trim() : "").filter((s) => s.length > 0).map((s) => ` - ${s.replace(/\s+/g, " ")}`);
6037
+ if (descs.length > 0) blocks.push(`- **${name}**:
6038
+ ${descs.join("\n")}`);
6039
+ } catch {
6040
+ }
6041
+ }
6042
+ if (blocks.length === 0) return "";
6043
+ return `
6044
+ ## Disciplines (always-on \u2014 L0 substrate)
6045
+ Follow these behavioral disciplines while doing the work:
6046
+ ${blocks.join("\n")}
6047
+ `;
6048
+ } catch {
6049
+ return "";
6050
+ }
6051
+ }
6052
+ function buildLanguageSection() {
6053
+ const code = process.env.HARNESSED_USER_LANG;
6054
+ if (!code) return "";
6055
+ const name = LANG_NAMES[code] ?? code;
6056
+ return `
6057
+ ## Language
6058
+ 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).
6059
+ `;
6060
+ }
6061
+ var PROTOCOLS = `
6062
+ ## Completion protocol
6063
+ When done emit: <promise>COMPLETE</promise>
6064
+
6065
+ ## Clarification protocol
6066
+ If you hit a gray area you cannot decide (\u22652 reasonable options, missing key context, ambiguous requirement), do NOT self-decide. Emit:
6067
+ STATUS: NEEDS_CLARIFICATION
6068
+ 1. <question>
6069
+ 2. <question>
6070
+ The main session will relay these to the user and re-spawn you with answers.
6071
+ `;
6072
+ async function resolveMaxIterations2(sub, packageRoot) {
6073
+ try {
6074
+ const path = resolve(packageRoot, "workflows", "defaults.yaml");
6075
+ const raw = await readFile(path, "utf8");
6076
+ const doc = parse(raw);
6077
+ const entry = doc?.ralph_max_iterations?.[sub];
6078
+ if (typeof entry === "number" && Number.isFinite(entry)) {
6079
+ return entry;
6080
+ }
6081
+ if (entry && typeof entry === "object") {
6082
+ for (const v of Object.values(entry)) {
6083
+ if (typeof v === "number" && Number.isFinite(v)) {
6084
+ return v;
6085
+ }
6086
+ }
6087
+ }
6088
+ return DEFAULT_MAX_ITERATIONS;
6089
+ } catch {
6090
+ return DEFAULT_MAX_ITERATIONS;
6091
+ }
6092
+ }
6093
+ function registerPrompt(program2) {
6094
+ program2.command("prompt").description(
6095
+ "Print a spawn-ready prompt for a sub-workflow (role + checklist + protocols, no spawn)."
6096
+ ).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) => {
6097
+ const packageRoot = getPackageRoot();
6098
+ const workflowsDir = resolve(packageRoot, "workflows");
6099
+ const rolePrompts = await loadRolePrompts(workflowsDir);
6100
+ const def = buildAgentDef(sub, rolePrompts, void 0, void 0, void 0);
6101
+ const body = def.prompt;
6102
+ const taskSection = typeof raw.task === "string" && raw.task.length > 0 ? `## Task
6103
+ ${raw.task}
6104
+
6105
+ ` : "";
6106
+ const toolsSection = await buildToolsSection(sub, packageRoot);
6107
+ const disciplinesSection = await buildDisciplinesSection(sub, packageRoot);
6108
+ const fullPrompt = `${taskSection}${body}
6109
+ ${toolsSection}${disciplinesSection}${PROTOCOLS}${buildLanguageSection()}`;
6110
+ if (raw.json) {
6111
+ const maxIterations = await resolveMaxIterations2(sub, packageRoot);
6112
+ const rp = rolePrompts[sub];
6113
+ const model = def.model ?? DEFAULT_MODEL;
6114
+ const specialist = rp?.specialist ?? DEFAULT_SPECIALIST;
6115
+ console.log(
6116
+ JSON.stringify({
6117
+ prompt: fullPrompt,
6118
+ max_iterations: maxIterations,
6119
+ model,
6120
+ specialist
6121
+ })
6122
+ );
6123
+ process.exit(0);
6124
+ return;
6125
+ }
6126
+ console.log(fullPrompt);
6127
+ process.exit(0);
6128
+ });
6129
+ }
6064
6130
  var PACKAGE_ROOT2 = getPackageRoot();
6065
6131
  var WORKFLOWS_DIR2 = join(PACKAGE_ROOT2, "workflows");
6066
6132
  function registerResearch(program2) {