Init workspace
One-time workspace setup for the ai-sdlc-harness pipeline. USER-ENTRY and HUMAN-ONLY — invoke only when the user explicitly runs /init-workspace; never autonomously, never from a subagent (guard-enforced).From its SKILL.md
npx -y skills add MostAshraf/ai-sdlc-harness --skill init-workspaceAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 15 stars15 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
11.6 KB, as published. Nobody here has run it
init-workspace — the interview (M7)
Human-only: the user's consent point for the whole workspace. Every command
below is ${CLAUDE_PLUGIN_ROOT}/bin/harness <verb> … — always the full
path; a bare harness is not on PATH, and shell variables set in one Bash
call do not persist to the next. Re-running refreshes one section at a
time (init-section), never a full-nuke.
0 · Environment bootstrap (do this FIRST)
Resolve the plugin root. Under native Qwen Code (no Claude-plugin
conversion), ${CLAUDE_PLUGIN_ROOT} is not exported until step 6 writes
.qwen/settings.json — and even then it takes effect next session, not
this one. Each Bash call is a fresh subprocess (an export won't
persist), so the probe prints the resolved path for you to substitute
textually. If the var is already set (Claude Code, converted install),
the probe passes it through unchanged:
# Resolve the plugin root and print it (native Qwen first-run fallback)
R="${CLAUDE_PLUGIN_ROOT:-}"
[ -z "$R" ] && for d in "$HOME/.qwen/extensions/ai-sdlc-harness/bin/harness" \
"$HOME/.qwen/extensions/ai-sdlc-harness/.qwen-extension-install.json"; do
[ -e "$d" ] && { case "$d" in *install.json) R=$(python3 -c \
"import json,sys;print(json.load(open(sys.argv[1])).get('source',''))" "$d");; \
*) R=$(dirname "$(dirname "$d")");; esac; [ -x "$R/bin/harness" ] && break; }; done
[ -x "$R/bin/harness" ] || { echo "ERROR: set CLAUDE_PLUGIN_ROOT to the dir with bin/harness" >&2; exit 1; }
echo "PLUGIN_ROOT=$R"
Use the printed PLUGIN_ROOT=<path> in place of every
${CLAUDE_PLUGIN_ROOT} below for this skill run.
The harness needs PyYAML; system pythons are often externally managed
(PEP 668), so the plugin owns a venv that bin/harness resolves
automatically on every future call. Bootstrap it through ONE dual-clause
command — same shape as hooks/run-guard's shell-agnostic launcher pair,
for the same reason: the shell that ends up interpreting this line is
picked by the platform, not by us. Claude Code's Bash tool always runs
bash (Git Bash on Windows), but Qwen Code's run_shell_command tool
shares hooks' shell-selection logic and falls back to cmd.exe on Windows
outside an MSYS-flavored terminal, where a NAME="$(command …)"-shaped
POSIX assignment is unparseable — cmd treats = as an argument
delimiter, the same failure class the hook launchers were fixed for:
exec "${CLAUDE_PLUGIN_ROOT}/bin/setup-venv" || ${CLAUDE_PLUGIN_ROOT}/bin/setup-venv
exec replaces bash with the launcher, so the second clause only ever
runs under cmd.exe (where exec fails fast and || falls through to the
unquoted path, PATHEXT-resolved to setup-venv.cmd). Both halves probe
the same two .venv layouts (bin/ POSIX, Scripts/ Windows) and the
same python3 → python fallback the old inline snippet used, then exit
immediately once PyYAML is already importable. Until this step runs,
bin/harness itself still works (it falls back to the same system
interpreter probe, which is what fails on a PyYAML-less system — that's why
this step exists), and the spawn/skill guards degrade open with a one-line
notice rather than erroring — expected pre-setup behavior, not a bug to chase.
1 · Must-provide (no defaults — ask)
- Work-item provider: local-markdown / github / github-projects / gitlab /
ado (CLI) / ado-mcp / jira / zoho (MCP — walk the user through the
model-in-the-loop probe). Two come in pairs:
ado=az boardsvsado-mcp;github= repo issues vsgithub-projects= a Projects v2 board. Plus specifics (stories dir,github_repo,github_project/_owner, …). - Git provider: local / github / gitlab / ado (CLI) / ado-mcp (MCP).
If the user didn't state one,
localis the sanctioned inference ONLY when every registered repo has no remote (git remoteempty) — say so in one line rather than asking; any repo with a remote → ask. - Repos:
name=pathper target repo.
Every init-section write is merged straight into the flat config by its
top-level keys, so provider, repos, and language payloads must be
self-nested under their own section key. If any init-section result
carries a notice key, relay its text to the user verbatim:
${CLAUDE_PLUGIN_ROOT}/bin/harness init-section --section provider --json \
'{"provider": {"work_item": "local-markdown", "git": "local", "stories_dir": "stories"}}'
${CLAUDE_PLUGIN_ROOT}/bin/harness init-section --section repos --json \
'{"repos": {"backend": "/path/to/backend", "frontend": "/path/to/frontend"}}'
overrides is the one exception on both counts: it's a flat grab-bag of
top-level config keys (status_mapping, subagent_models, quick_mode,
…), never self-nested under an "overrides" key, and unlike
provider/repos/language (each write replaces the whole file — always
send the complete current set) its writes merge, so separate
--section overrides calls for different settings accumulate rather than
clobbering each other. See step 3.
2 · Discovered, then confirmed
Run ${CLAUDE_PLUGIN_ROOT}/bin/harness discover --repo <path> per repo. discover first
ensures the repo is clean and on its default branch (ensure_default_branch
— the same reusable precondition preflight uses later): a dirty repo, or
one mid-rebase/merge, refuses with a clear error — surface it to the user
(never auto-stash/discard/continue) — and a clean repo on a different
branch is switched, reported back in branch_check so the interview can
tell the user it happened. If the guessed default branch doesn't exist
locally (no resolvable origin/HEAD), pass --branch <name> explicitly —
note this only catches a nonexistent guess; a repo with no origin and
a stray local branch that happens to be named main cannot be told apart
from a genuine one, so confirm the branch name with the user for any repo
without a resolvable origin/HEAD.
Known risk: re-running this against a repo with an active /dev-workflow
run switches that run's feature-branch checkout back to default — and the
switch flips the whole checkout, so avoid discovery on ANY path inside a
checkout with in-flight work, not just the exact registered path. Present the proposals (language, test_cmd, default
branch) as defaults-to-confirm. A monorepo_split proposal means this "one repo" is several
logical repos sharing one .git — register each proposed root as its own repo: path
<checkout>/<root> (checkout-relative; . = the checkout), name <repo>-<root>, and confirm
each test_cmd FROM that subtree — commands run with the registered path as cwd. init-verify
passes any path inside a git work tree, naming the enclosing checkout: a pass, not a warning.
What the shared .git costs: tasks still isolate (worktrees cut from the physical checkout,
staging subtree-bounded, direct-branch fallback refused), but outside them both repos ride one
branch, and a parent root holds its child — only review keeps a parent's task out of it.
Language-config is per repo, under language.repos, keyed by the same
names used in --section repos (a sub-key, not a sibling of the global
test_paths/test_closure settings, so a repo name can never collide with
those) — confirm each repo's own test_cmd by running it, never collapse
differing repos onto one command. A proposal may carry no test_cmd —
no single command covers that root (a .NET root with no solution file, or
two side by side). Ask the user; never synthesise one. Nothing catches a bad
guess later: init-verify gates on invocability only, so a command that
cannot even locate its project still reports pass, and the first
verify-red then seals a red-proof over a build error. coverage_cmd gets the same
treatment: discover proposes one only on repo evidence (a coverage
script, jest/vitest+provider, jacoco in the pom) — confirm it by running
it. No proposal → ask the user for one (the harden step consumes it and
never improvises); an explicit skip is a valid answer, recorded by simply
omitting the key — tell the user harden will re-ask at run time. Write the
whole set in one --section language call, e.g. {"language": {"repos": {"backend": {"test_cmd": "sh mvnw -q test", "coverage_cmd": "sh mvnw -q test jacoco:report"}, "frontend": {"test_cmd": "npm test"}}}}.
A repo carrying a known-failing spec unrelated to any run can declare a
quarantine sibling — {"exclude_template": "--exclude {test}", "tests": [{"test": "…", "reason": "…", "since": "YYYY-MM-DD"}]} — applied to the
test command and to coverage (give coverage its own
coverage_exclude_template when it's a different tool). reason and
since are required; the template is the FLAG only, must contain {test},
and is runner-specific — never guessed. Flags are APPENDED, so the command
must be a single one (no unquoted &&/|/;) and the flag must reach the
runner: an npm test wrapper swallows --exclude unless the template
passes it through (-- --exclude {test}), so prefer invoking the runner
directly. Test paths are repo-relative, forward-slashed, no ./ prefix.
Each run logs one flagged event naming the exclusions.
Don't offer this proactively at setup; it's for a failure the user already
has.
3 · Choose-or-default (offer "default" explicitly, every time)
Status-mapping override (provider defaults usually suffice), change-types +
naming templates, subagent_models (default all inherit), quick-mode
thresholds/keywords, repo-map staleness N, review-policy team rules,
security.scan_cmd (if a scanner is configured, it's per-repo-keyed the
same way language is — no scanner configured stays informational-only).
Only what the user changes goes in --section overrides — shipped
defaults cover the rest. Unlike provider/repos/language, this payload
is flat, not self-nested (these are top-level config keys in their own
right), e.g. --section overrides --json '{"quick_mode": {"loc_max": 50}}' — never {"overrides": {...}}. Each call deep-merges into whatever is
already there, so it's fine to write these one setting at a time as the
user decides them; there's no way to unset a previously-written override
through this verb though — that needs a direct edit to overrides.yaml.
4 · Verify (a real gate) + finish
${CLAUDE_PLUGIN_ROOT}/bin/harness init-verify— every check must pass (or bemanualwith the user's explicit acknowledgment for MCP providers). Failures show remediation; fix and re-run. Do not proceed on failures.${CLAUDE_PLUGIN_ROOT}/bin/harness init-finalize— writes the permissions allowlist and the bootstrap marker (section writes alone do not write either of these). It re-runs the same verify gate itself and refuses (exit 1) if any check still fails, so it can't mark a half-configured workspace bootstrapped even if step 1 above was skipped by mistake. Confirm.claude/settings.jsonmerged cleanly (non-destructive).- Repo-map: offer to generate one now per repo, following
/repo-map-refresh's step 2 exactly (subagent_type-guessing warning,harness-mode: repo-mapheader, and stamp-it-yourself rule all live there — this file doesn't keep its own copy). - Tell the user:
/dev-workflow <work-item-id>is ready.
5 · Adding a repo after the fact
Use the dedicated /add-repo skill — it's the one place this procedure
(discover → confirm → register → verify → finalize) is maintained; don't
hand-roll it here too.