scripts/bash/resolve-hierarchy-strategy.sh
#!/usr/bin/env bash
# resolve-hierarchy-strategy.sh — pick the epic hierarchy strategy from a
# capability probe result (the capability-aware hierarchy decision).
# PowerShell twin: scripts/powershell/resolve-hierarchy-strategy.ps1.
#
# The orchestrator probes the tracker before any hierarchy use. This
# resolver turns the probe result into a deterministic strategy, so the
# choice is a computation rather than an in-prompt judgement. The
# connector still performs every tracker call; this only answers
# *which shape to use and what to disclose*.
#
# Usage:
# resolve-hierarchy-strategy.sh <payload.json>
# ... | resolve-hierarchy-strategy.sh # payload on stdin
#
# Input (JSON): instance, version, probe_fresh, probe_from_instance,
# probe_from_version, capabilities {epic_type, issue_parent_task_children,
# work_item_convert}, use_time_failure (optional: the failed operation).
# Output (JSON): strategy (native_epic | issue_parent_task_children |
# degraded_no_tree), attach (bool), convert (bool), disclosure (string),
# degraded (bool), reason.
#
# A stale probe, or a probe from another instance or version, counts as
# no probe: the strategy is degraded_no_tree.
set -euo pipefail
die() { echo "resolve-hierarchy-strategy: $*" >&2; exit 2; }
if [ "${1:-}" = "-h" ] || [ "${1:-}" = "--help" ]; then
cat <<'USAGE'
resolve-hierarchy-strategy.sh — pick the epic hierarchy strategy
Usage:
resolve-hierarchy-strategy.sh <payload.json>
... | resolve-hierarchy-strategy.sh # payload on stdin
Input (JSON): instance, version, probe_fresh, probe_from_instance,
probe_from_version, capabilities, use_time_failure (optional).
Output (JSON): strategy, attach, convert, disclosure, degraded, reason.
USAGE
exit 0
fi
if [ $# -gt 1 ]; then die "expected at most one payload JSON path (or payload on stdin)"; fi
payload=""
if [ $# -eq 1 ]; then
[ -r "$1" ] || die "cannot read input: $1"
payload="$(cat "$1")"
else
payload="$(cat)"
fi
# node is the JSON engine (same dependency as open-mr.sh and
# resolve-mr-ownership.sh); the decision logic below is the bash twin's
# single source of truth.
RESOLVER_JS="$(cat <<'NODE_EOF'
const fs = require("node:fs");
const raw = fs.readFileSync(0, "utf8");
let payload;
try { payload = JSON.parse(raw); } catch { console.error("resolve-hierarchy-strategy: invalid payload JSON"); process.exit(2); }
if (payload === null || typeof payload !== "object" || Array.isArray(payload)) {
console.error("resolve-hierarchy-strategy: expected a JSON object payload"); process.exit(2);
}
const instance = payload.instance || "unknown instance";
const version = payload.version || "unknown version";
const caps = payload.capabilities || {};
const fresh = payload.probe_fresh === true;
const sameTarget = (payload.probe_from_instance === payload.instance) && (payload.probe_from_version === payload.version);
const useFail = payload.use_time_failure || null;
function disclosureLine(strategy) {
return "Hierarchy probe on " + instance + " " + version + ": strategy " + strategy + ".";
}
let out;
if (!fresh || !sameTarget) {
out = {
strategy: "degraded_no_tree",
attach: false,
convert: false,
disclosure: disclosureLine("degraded_no_tree") + " Probe is stale or from another target; no hierarchy used.",
degraded: true,
reason: "stale-or-foreign probe counts as no probe",
};
} else if (useFail) {
out = {
strategy: "degraded_no_tree",
attach: false,
convert: false,
disclosure: disclosureLine("degraded_no_tree") + " Hierarchy operation " + useFail + " failed at use time; fell back for this epic only.",
degraded: true,
reason: "use-time failure downgrades only the affected epic",
};
} else if (caps.epic_type === true) {
out = {
strategy: "native_epic",
attach: true,
convert: false,
disclosure: disclosureLine("native_epic") + " Native epic type carries the children; no conversion needed.",
degraded: false,
reason: "best available: native epic type",
};
} else if (caps.issue_parent_task_children === true) {
out = {
strategy: "issue_parent_task_children",
attach: true,
convert: caps.work_item_convert === true,
disclosure: disclosureLine("issue_parent_task_children") + " Issue parent holds Task children" + (caps.work_item_convert === true ? "; children convert from Issue to Task and the plan output names the trade-off." : ".") ,
degraded: false,
reason: "best available: issue parent with task children",
};
} else {
out = {
strategy: "degraded_no_tree",
attach: false,
convert: false,
disclosure: disclosureLine("degraded_no_tree") + " Tracker has no hierarchy support; label convention used.",
degraded: true,
reason: "no hierarchy capability; degraded mode, never silent",
};
}
process.stdout.write(JSON.stringify(out) + "\n");
NODE_EOF
)"
printf '%s' "$payload" | node -e "$RESOLVER_JS"
SKILL.md
---
name: factory-mission
description: Use when executing a spec-gated mission from the queue — inner loop specify → plan → implement ↔ converge with tracker integration, worktree isolation, and a circuit breaker.
---
# factory-mission
> **Scope rule**: scripts stay skill-local — never source a script from another skill. For another skill's operations, invoke that skill (e.g. `architect-clarify`, `product-clarify`, `team-boot`) via the Skill tool; for its documents, follow the `Requires` line. `scripts/` and `references/` below are relative to this skill. `{REPO_ROOT}` denotes the project root (where `.adlc/` lives).
## In a factory run — read this first
When `ADLC_RUN_ID` is set, the workflow engine runs this skill unattended as its `execute` step, and the completion gate follows it. Do exactly this:
1. **The mission is the run's issue.** Take it from `--issue <ref>` in the arguments; when that is missing or empty, use `ADLC_ISSUE_REF`. With neither, do not ask: write `{"conclusion":"blocked: factory run without an issue"}` into `ADLC_STEP_OUTPUT` and return.
2. Run the mission for that issue end to end (Phases 0–6 below): brief, specify, clarify, plan, test (RED), code (GREEN), verify, converge, then Invoke the `factory-runbook` skill to open or reuse the draft code MR/PR in the repository the change belongs to.
3. **Before you return, always:** write `mr_url` (the code MR/PR you opened or reused) and a one-line `conclusion` into `ADLC_STEP_OUTPUT` — one flat JSON object of string values; keep any keys already in the file. The engine links `mr_url` at the completion gate. Do this whatever the outcome; with no MR, write only the `conclusion`.
4. **Never ask a question or wait for an answer.** The completion gate after this step is the approval; a question here ends the step with nothing implemented.
**Requires:** the `factory-runbook` skill (provides the publish procedure and run contracts): `adlc-cli skills add tikalk/adlc-team-skills --skill factory-runbook`
## In a local session — you answer the gates
When `ADLC_RUN_ID` is not set, a person is in this session, and nothing else will answer a gate. The agentic-container runtime that relays gates to Slack exists only in a runner pod.
1. **A paused run is your turn, not the end of the mission.** When `adlc-cli workflow run` or `resume` exits with the run paused at a gate (`run_paused`, `status: paused` in `adlc-cli workflow state show <run_id>`), ask the person the gate's question and options. Then run `adlc-cli workflow resume <run_id> --input <verdict_input>=<answer>` and keep going. Never end your turn on a paused run that nobody was asked about.
2. **Every gate that approves MRs is followed by a step that opens them.** A `gate` whose message promises MRs/PRs is followed by a `publish` step: Invoke the `factory-runbook` skill for each repository the change touches, writing `mr_url` (`mr_urls` for more than one). Only then comes the write-back step, so the completion comment links real MRs. A gate with nothing after it to act on its answer is a plan defect; fix the plan before you start the run.
3. Before ending your turn, the run is `completed`, `failed` or aborted, or it is paused at a gate the person has been asked about.
---
## What this skill does
`factory-mission` is the execution-harness orchestrator of the software factory. It takes a feature description, hands brief construction to `mission-brief` (goal, constraints, non-goals, success criteria — `mission-brief/references/mission-brief-template.md`), generates an ordered **step list**, and executes those steps via sequential subagent runs.
It implements the following key factory platform capabilities:
1. **Skill routing via the harness**: step prompts name skills by exact name; the dispatched subagent resolves them through its own Skill tool (the runtime injects the installed inventory — no filesystem scan). For commands-based frameworks the model cannot see, consult `references/agent-integrations.md`.
2. **Tracker-Agnostic Integration** (`factory-runbook/references/tracker-integration.md`): When invoked with `--issue <ref>`, it pulls ticket context, respects automation/dispatch labels (`autonomous`/`supervised`), and writes back status comments + iteration logs.
3. **Inter-Agent Comment Bus** (`factory-runbook/references/tracker-integration.md` §Inter-Agent Comment Bus): When tracker-integrated, each step's terminal output (decisions, findings, artifact references — never drafts) is published as a structured comment on the PR/MR/issue. The next step reads previous markers before starting. This is the durable inter-agent memory that survives session boundaries, runtime switches, and pod crashes. Drafts stay on local disk.
4. **Self-Contained Worker Brief**: The Mission Brief is persisted to `.adlc/workflows/runs/<run_id>/brief.md` as a draft. Resumed runs and cross-runtime workers read it from disk — no session-context dependency.
5. **Worktree Isolation**: Each run gets its own git worktree. Never touches the user's main checkout. Cleaned up on exit (retained if unsaved work).
6. **Lease-Based Liveness**: The state file carries a renewable lease with heartbeat + TTL. Resume can distinguish live, stale, and completed runs.
7. **Stall Detection**: After dispatching a subagent, observable progress is checked at a configurable window (default 20 min). A hung agent that passes the circuit breaker is detected and killed.
8. **Lane-Based Dispatch** (`factory-runbook/references/lanes.md`): Steps can run on different lanes — `inline` (this session), `agent` (fresh session of same CLI for maker/checker separation), or `cli:<runtime>` (optional cross-vendor). The `agent` lane is the default for unattended stages.
9. **Scratchpad Tools**: Subagents share named, run-private scratchpads (`.adlc/workflows/runs/<run_id>/scratchpads/<name>.txt`) to compile notes, drafts, and reviews incrementally before publishing.
10. **Workflow Memory & Self-Improvement**: Persistent JSONL database (`.adlc/workflows/memory.jsonl`, workspace-global) stores learnings across runs. `factory-learn` periodically runs retrospectives to prune/weight memories.
11. **Hierarchical Context Parameters**: Workflows and agents reference parameters as `{{params.<key>}}`, resolved from most specific to least specific: `agent < workflow < repository < project < default`.
12. **Decoupled Test/Code Separation**: Test-first build with mechanical gates plus converging judgement lives in `mission-brief`.
---
## When to use
- "Build this feature end to end" inside a factory-enabled team.
- You want an execution loop with a circuit breaker, score-regression checking, and a robust resume mechanism (`factory-mission --resume`).
- You want to run autonomously against a ticket queue.
**When NOT to use**:
- For non-factory standalone projects (run `adlc-cli workflow run <workflow.yml>` — the CLI engine, shared run state).
- Trivial 1-line changes (do them directly).
---
## Process
`factory-mission` executes in alignment with the shared executor contract (`factory-runbook/references/executor.md`) and the tracker-agnostic layer (`factory-runbook/references/tracker-integration.md`).
### Phase 0 to 4: Setup & Compilation
1. Read the run's `mission.yml` (`.adlc/workflows/runs/<run_id>/mission.yml`). Resolve execution and supervision.
2. If `--issue <ref>` is specified (in a factory run, `ADLC_ISSUE_REF` when it is not):
- Discover credentials and MCP/CLI tools (`factory-runbook/references/tracker-integration.md`).
- Pull the issue content as the primary Brief description.
- Read the labels. If dispatch is `interactive` or gating is `human-required` -> **HALT execution** (hand back to interactive session).
- **Dependency hard gate (Operation 12):** read the issue's `**Depends on**` line, resolve each dependency's state via the connector, and refuse when any dependency is still open (only `closed` / `cancelled` count complete — `spec-gated` does NOT satisfy the gate). On refusal: publish a `blocked: dependency-gate` comment naming the blocking issue(s), set the issue's lifecycle label back to `spec-gated` through the Set Lifecycle step (tracker-integration Operation 9), release the lease (`ADLC_STEP_OUTPUT` conclusion `blocked: dependency-gate on <refs>`), and return without touching worktree, code, or executor. When the tracker exposes native `blocked_by`/`blocking` relations (GitLab issue links, Linear/Jira relations), use them in addition to the brief's Depends-on line — a `Blocked`/`blocked-by` signal from the tracker also triggers the gate.
- When the gate passes, move the issue to `executing` through the Set Lifecycle step (Operation 9; the lease ACQUIRE of Operation 10 does this). Lane runs arrive already `executing`: the step then changes nothing.
- The comment bus is active for this run — step outputs will be published as marker comments on the PR/MR/issue.
3. If no issue (outside a factory run): read spec description from arguments. The comment bus is inactive — steps communicate through local files only.
4. Validate intake and hand the brief to `mission-brief` — brief construction lives there (per `mission-brief/references/mission-brief-template.md`). The intake bundle (validated intake) plus the `Framework lane` line the session model resolves (see step 5) is a `draft` — not published to the comment bus.
5. Resolve the framework lane per `mission-brief`'s detection section (a `framework:` pin in mission.yml overrides it) and record the disclosed `Framework lane: <lane> (<evidence>)` line in the run brief. If no framework is installed, disclose that and fall back to Default.
6. Resolve hierarchical Context Parameters from `agent < workflow < repository < project < default` and embed the frozen value map in the brief.
7. Generate the step list based on route classification (`spec`, `change`, `quick`). Each step declares `output_type` (`draft`/`decision`/`findings`/`artifact-ref`) and `reads_from` (markers or local paths) per the executor contract. Step prompts carry the exact skill name; the subagent resolves it via its Skill tool. If the target is a commands-based framework, check the commands dir per `references/agent-integrations.md`; an absent dir means the step executes directly.
**Team index fallback:** when no team record-class index was injected at session start, read the binding records directly from `docs/adlc/memory/` (dual-read order: `docs/adlc/memory` first, legacy `.adlc/memory` fallback) and state that fallback in one line. Never block on the missing injection.
## Lifecycle DAG & Step Resolution
`factory-mission` resolves each lifecycle step to `mission-brief`, the lane-carrying core skill that owns it. The `brief` step is produced by `mission-brief` during Phase 0 intake, before the workflow is compiled; the table below is the dispatched lifecycle. Step order, inputs, and outputs follow the table; the owning skill defines the discipline.
| Step | Skill | output_type | reads_from |
| --- | --- | --- | --- |
| specify | mission-brief | draft | `brief.md` |
| clarify | mission-brief | decision | `spec.md` |
| plan | mission-brief | draft | `spec.md`, `clarify` |
| test | mission-brief | artifact-ref | `spec.md` |
| code | mission-brief | artifact-ref | `test`, `spec.md`, `plan.md` |
| verify | mission-brief | findings | `test`, `code` |
| converge | mission-brief | decision | `brief.md` |
A `SPEC_CORRECTION_NEEDED` verdict routes to the owning clarify gate (`mission-brief` clarify), never to code.
## Boundary
`factory-mission` owns the execution harness: the worktree, the lease, tracker integration (issue intake, the comment bus, completion write-back), and the draft code MR. It never owns spec content, tests, code, or verdicts — each step's output belongs to the `mission-brief` skill that produced it. Dispatch names phases, not commands: every lifecycle step resolves through the table above.
## Shared Executor Overrides
Knobs specific to a mission run, read from `mission.yml` (the policy home for execution and supervision):
1. **Supervision**: the run's supervision mode — unattended runs never ask a question.
2. **Quality threshold**: `quality_threshold` travels in as a converge input owned by `mission-brief`; the orchestrator carries it, never applies it.
3. **Test/code split**: selected per run — whether the build dispatches separate test and code steps.
### Phase 5: Converge Loop Control
The orchestrator runs `test`/`code` → `verify` → `converge` as a loop and owns the loop's counters; the verdict itself belongs to `mission-brief`.
1. **DONE** at or above `quality_threshold` → the loop exits and the run proceeds to Phase 6. `DONE` below `quality_threshold` is treated as `CONTINUE`.
2. **CONTINUE** → increment `consecutive_tasks_appended` (persisted in the run state `runs/<run_id>/state.json`, surviving resume), attach the verify findings, and loop back to `test` (TDD split on) or `code`. When the counter reaches `circuit_breaker` from `mission.yml` (default 3), stop the run as failed: "circuit breaker: N consecutive iterations did not converge — human review needed."
3. **Score regression:** track `consecutive_score_regressions` (persisted in the run state, surviving resume). A converge quality score below the previous iteration increments it; equal or higher resets it to 0. Reaching `circuit_breaker` stops the run: "N consecutive score regressions — quality trending downward, human review needed."
4. **SPEC_CORRECTION_NEEDED** → the owning clarify gate, bounded by `max_spec_corrections` (default 2); exceeding it halts for human review.
5. The `code`-step retry loop (`mission-brief` GREEN gate) never consumes these counters — only completed iterations do.
### Phase 6: Completion & Write-Back
1. If tracker-integrated: read all marker comments from the PR/MR/issue to compile the audit trail (converge decisions, test findings, implement artifact references, convergence history).
2. Close the shared run: final `adlc-cli workflow state advance <run_id> --step <last> --status completed` (the run dir is the durable archive; no separate move).
3. Write per-implement logs to `iterations.md`.
4. If tracker-integrated:
- Post completion summary as a ticket comment (marker: `factory-mission:status=completed:run=<run_id>`).
- Move the issue from `executing` to `validation` (or `done` if merged) through the Set Lifecycle step (tracker-integration Operation 9), which also removes any other lifecycle label.
- Stamp `agent-authored` on opened PRs.
- In a factory run, write the code MR/PR URL as `mr_url` into `ADLC_STEP_OUTPUT` (see "In a factory run" above). Open it via the runbook with `--kind code` so the mission branch (`factory/mission/*`, `factory/code/*`) and mission title are used.
- Defer PR merge to code-owner approval (never auto-merge without approval).
5. Output the complete audit summary.