Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,22 @@ on:
paths:
- 'dist/.claude/skills/**'
- 'dist/.codex/skills/**'
- 'dist/.qoder/skills/**'
- 'dist/.qwen/skills/**'
- 'cli/src/bin/gen_codex_skills.rs'
- 'cli/tests/qoder_skills_test.rs'
- 'cli/tests/qwen_skills_test.rs'
- '.github/workflows/ci.yml'
push:
branches: [main]
paths:
- 'dist/.claude/skills/**'
- 'dist/.codex/skills/**'
- 'dist/.qoder/skills/**'
- 'dist/.qwen/skills/**'
- 'cli/src/bin/gen_codex_skills.rs'
- 'cli/tests/qoder_skills_test.rs'
- 'cli/tests/qwen_skills_test.rs'
- '.github/workflows/ci.yml'

permissions:
Expand Down Expand Up @@ -41,3 +49,25 @@ jobs:
# produce from dist/.claude/skills/. Fix locally with:
# cd cli && cargo run --bin gen_codex_skills --features dev-tools
cargo run --quiet --bin gen_codex_skills --features dev-tools -- --check

skill-mirror-parity:
name: Qoder/Qwen skills mirror the Claude source
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable

- name: Install OpenSSL (Linux)
run: sudo apt-get update && sudo apt-get install -y libssl-dev pkg-config

- name: Verify .qoder/skills and .qwen/skills mirror .claude/skills
run: |
# Qoder and Qwen Code both parse the full Claude-format frontmatter,
# so their trees are byte-for-byte copies rather than generated
# variants. Fix locally with:
# rm -rf dist/.qoder/skills dist/.qwen/skills
# cp -a dist/.claude/skills dist/.qoder/skills
# cp -a dist/.claude/skills dist/.qwen/skills
cargo test -p straymark-cli --test qoder_skills_test --test qwen_skills_test
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
---
id: AILOG-2026-08-05-003
title: Qwen Code agent surface (QWEN.md + .qwen/skills) and the update path that would have withheld it
status: accepted
created: 2026-08-05
agent: claude-opus-5-v1.0
confidence: high
review_required: false
risk_level: medium
eu_ai_act_risk: not_applicable
nist_genai_risks: []
iso_42001_clause: []
lines_changed: 445
files_modified:
- dist/dist-manifest.yml
- dist/dist-templates/directives/QWEN.md
- dist/dist-templates/directives/AGENTS.md
- dist/.qwen/skills/
- dist/STRAYMARK.md
- dist/.straymark/00-governance/AGENT-RULES.md
- cli/src/main.rs
- cli/src/commands/install_skills.rs
- cli/src/commands/update_framework.rs
- cli/src/commands/remove.rs
- cli/src/commands/validate.rs
- cli/tests/qwen_skills_test.rs
- cli/tests/inject_test.rs
- cli/tests/architecture_skill_test.rs
- .github/workflows/ci.yml
- README.md
- docs/adopters/CLI-REFERENCE.md
- docs/adopters/ADOPTION-GUIDE.md
- CHANGELOG.md
observability_scope: none
tags: [multi-agent, qwen-code, qoder, directive-injection, update-path, ci, i18n]
related:
- AIDEC-2026-08-05-001
- AILOG-2026-08-04-002
---

# AILOG: Qwen Code agent surface

## Summary

Qwen Code went from zero integration to a first-class surface — `QWEN.md`
directive injection, 15 skills at `.qwen/skills/`, `install-skills --agent
qwen`, `validate --agent qwen`, `remove` cleanup. Along the way, two defects
that the review surfaced were fixed: `update-framework` never created
newly-declared injection targets (so the new surface would have reached fresh
installations only), and the Qoder documentation asserted the opposite of what
the Qoder runtime does.

## Context

The question asked was narrow — *do the Qwen and Qoder CLIs get the same rules
and configuration Claude Code gets, at `init` and at later `update`?* — and the
answer split cleanly:

- **Qoder**: yes for skills (`dist/.qoder/skills/`, #399) and yes for rules,
via `AGENTS.md`. Verified in the shipped application: it file-searches
`**/AGENTS.md` and adds each match as an instruction source.
- **Qwen Code**: no, on both counts. Nothing referenced it anywhere in the
product; the only occurrences in the repo were prose inside the audit skills
warning auditors not to write `qwen-code` in the `auditor:` field.

The severity of the Qwen gap depended on one empirical question — does Qwen
Code read `AGENTS.md`? — so it was answered against the installed runtime
rather than from memory. Its context-filename resolver ends in
`["QWEN.md"] : Array.isArray(configured) ? configured : [configured]`: the
default is `QWEN.md` alone. `AGENTS.md` appears exactly once in the bundle, in
a system-prompt string enumerating files the agent must not modify without
permission — recognized, never loaded. So Qwen Code users were receiving no
governance at all, which is a strictly worse position than Codex or Qoder.

Two further runtime facts settled the design (see [AIDEC-2026-08-05-001] for
the alternatives): `Storage.getGlobalQwenDir()` resolves `$QWEN_HOME` →
`$HOME/.qwen`, and skills resolve at **both** project and user scope with
`allowed-tools` supported. The channel is therefore a byte-for-byte mirror of
`.claude/`, exactly as Qoder's is.

## Changes

**Framework (`dist/`)**

- `dist/.qwen/skills/` — 15 skills, byte-identical to `dist/.claude/skills/`.
- `dist/dist-templates/directives/QWEN.md` — cloned from the `GEMINI.md`
template (Qwen Code is a Gemini CLI fork) with the `qwen-code-v{version}`
identity.
- `dist-manifest.yml` — `.qwen/skills/` in `files:`, `QWEN.md` in `injections:`.
- `AGENT-RULES.md` (EN/es/zh-CN) and the `AGENTS.md` template gained
`qwen-code-v1.0` **and** `qoder-v1.0`; the latter had been missing since #399.
- `STRAYMARK.md` § "Directive Injection Markers" lists `QWEN.md`.

**CLI**

- `install_skills.rs`: `--agent qwen` branch + `resolve_qwen_home()` mirroring
the runtime's own resolution. The doc-comment now states which agents
*require* the user-level install (only Codex) and which merely benefit.
- `validate.rs`: `validate_codex_skills()` generalized to
`validate_agent_skills(agent)` behind an `AgentSkillsSpec`. The `claude-only-key`
check is now conditional — Qoder and Qwen legitimately carry `allowed-tools`,
so flagging it there would have been a false positive by construction.
- `remove.rs`: `.qwen/skills`, the `.qwen` parent, and `QWEN.md` in
`LEGACY_DIRECTIVE_TARGETS`.
- `update_framework.rs`: dropped the `if !target_path.exists() { continue; }`
guard — see §Risk R1.

**CI** — new `skill-mirror-parity` job. The `.qoder` mirror had a parity test
since #399, but the pipeline runs no `cargo test`, so the only gate in CI was
`.codex`'s generator check. The job runs exactly the two mirror suites, which
keeps it narrow enough not to reopen the deferred "no full test suite in CI"
decision.

**Docs (EN/es/zh-CN)** — Qwen added to README (agent list, architecture tree,
directory table, platform table), CLI-REFERENCE (`install-skills`,
`validate --agent`, "5 parallel forms" → 6) and ADOPTION-GUIDE. The i18n
READMEs also gained Qoder, which #399 had updated only in English.

## Verification

- `cargo test`: 949 passed, 0 failed.
- New tests: `qwen_skills_test.rs` (tree parity, `install-skills` e2e under
`QWEN_HOME`, manifest surface, `validate --agent` acceptance);
`update_framework::tests::update_creates_injection_targets_that_are_missing_on_disk`;
`inject_test::test_manifest_declares_every_directive_injection` (was
`AGENTS.md`-only, now all seven targets plus template-on-disk).
- Manual e2e against a simulated post-`init` tree: `install-skills --agent qwen`
installed 15 skills into a temp `$QWEN_HOME`; `validate --agent qwen` reported
all 15 passing with no `allowed-tools` false positives; `validate --agent
qoder` degraded to its install hint on an empty directory.

`straymark init` downloads the published release ZIP, so it cannot exercise a
local `dist/`. The `init`-level check of `QWEN.md` is therefore deferred to
after the `fw-4.41.0` tag lands.

## Risk

| Id | Risk | Handling |
|----|------|----------|
| R1 | `update-framework` creating missing targets means a directive file the operator deleted on purpose is now restored on the next update. | Accepted: `STRAYMARK.md` § "Directive Injection Markers" already documents exactly this ("no opt-out per target short of editing the manifest"), and `repair` has always behaved this way. The code was the outlier, not the doc. If adopters turn out to delete these deliberately, the fix is a per-target opt-out in `config.yml`. |
| R2 (new, not in Charter) | The manual e2e caught that `validate --agent`'s clap `value_parser` is declared in `main.rs`, separate from the dispatch in `validate.rs` — widening only the latter left the flag rejected at parse time. | Fixed, and pinned by `validate_accepts_every_user_level_agent`. Worth remembering as a shape: this CLI keeps its accepted-value lists at a distance from the code that consumes them, so `install-skills` and `validate` each need both ends touched. |
| R3 (new, not in Charter) | Six directive files now land in an adopter's project root at `init`, and the count grows with every supported CLI. | Not addressed here. Noted as a real ergonomics cost of the per-CLI directive model; the alternative (AGENTS.md only) is not available while runtimes like Qwen Code default to their own filename. |

## Follow-ups

- Verify `QWEN.md` and `.qwen/skills/` land through a real `straymark init` and
a real `straymark update` once `fw-4.41.0` is published, and confirm in the
live `qwen` and `qoder` CLIs that the context file and skills are picked up.
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
---
id: AIDEC-2026-08-05-001
title: Qwen Code as a first-class agent surface — mirror the Claude channel, and make update-framework create new injection targets
status: accepted
created: 2026-08-05
agent: claude-opus-5-v1.0
confidence: high
review_required: false
risk_level: medium
eu_ai_act_risk: not_applicable
nist_genai_risks: []
iso_42001_clause: []
tags: [multi-agent, qwen-code, qoder, directive-injection, dist-manifest, update-path]
related:
- AILOG-2026-08-05-003
- AILOG-2026-08-04-002
---

# AIDEC: Qwen Code as a first-class agent surface

## Context

A review of StrayMark's integration with the Qwen Code and Qoder CLIs — asked
for both at `init` time and at later `update` time — surfaced one silent hole
and one false claim.

**Qwen Code had zero integration.** No `dist/.qwen/`, no manifest entry, no
`install-skills` branch, no row in any compatibility table. That matters more
than the absence suggests: verified against the installed runtime
(`@qwen-code/qwen-code`), its context-file resolver falls back to `["QWEN.md"]`
when nothing is configured, and `AGENTS.md` appears only in a safety string
listing files the agent must not modify without permission. A Qwen Code user
therefore received **no StrayMark governance at all** — not even the AGENTS.md
fallback that covers Codex and Qoder.

Two runtime facts shaped the design, both read directly from the shipped code
rather than assumed:

- `Storage.getGlobalQwenDir()` returns `$QWEN_HOME` when set, `$HOME/.qwen`
otherwise; user-level skills live at `<dir>/skills/`.
- Skills resolve at **both** scopes — `<projectRoot>/.qwen/skills/` and the
global dir — and the frontmatter parser accepts `allowed-tools`.

**Qoder's documentation was wrong.** README and CLI-REFERENCE (three languages)
stated Qoder reads skills from `~/.qoder/skills/` "not from the project tree".
The application bundle resolves a `Project` scope
(`<projectRoot>/.qoder/skills/`) and watches it, so `install-skills --agent
qoder` is a convenience, not a prerequisite. It also searches `**/AGENTS.md`
and injects the matches as instructions, which is why Qoder was in fact
governed while Qwen Code was not.

## Problem

Two decisions had to be made together, because the second determines whether
the first reaches anyone who already installed StrayMark.

1. What shape should the Qwen Code channel take — which skill frontmatter, and
which directive file?
2. `update_framework.rs` skipped every injection target absent from disk
(`if !target_path.exists() { continue; }`). A new agent surface added to the
manifest would therefore land only on fresh `init`s. Meanwhile
`STRAYMARK.md` § "Directive Injection Markers" states that `init`,
`update-framework` **and** `repair` all "create any missing target file".
Code and documentation contradicted each other; one of them had to move.

## Alternatives Considered

### Alternative 1: Mirror `.claude/` byte-for-byte, pinned by a parity test

**Description**: `dist/.qwen/skills/` is a straight copy of
`dist/.claude/skills/`, guarded by the same shape of test that guards Qoder
(#399), plus a CI job. `QWEN.md` clones the `GEMINI.md` template with a
`qwen-code-v{version}` identity.

**Pros**:
- Qwen Code parses `allowed-tools`, so the richer frontmatter is usable, not
merely tolerated. Stripping it would lose real tool-scoping information.
- Identical to the precedent already set for Qoder — one pattern, not two.
- A parity test states the invariant in one line and fails loudly on drift.

**Cons**:
- Duplicates 15 files in the repo (~2,300 lines) that a generator could emit.

### Alternative 2: Clone `.gemini/` (minimal frontmatter)

**Description**: Qwen Code is a Gemini CLI fork, so ship the reduced
`name` + `description` frontmatter.

**Pros**:
- Conservative if the parser turned out to be strict about unknown keys.

**Cons**:
- Contradicted by the runtime: 94 `allowedTools` references in the bundle, and
`allowed-tools` present in the skill frontmatter path. The conservatism buys
nothing and discards tool scoping.

### Alternative 3: Generate `.qwen/` from `.claude/` like `.codex/`

**Description**: Add a `gen_qwen_skills` binary and a `--check` CI gate.

**Pros**:
- No duplicated content in the repo.

**Cons**:
- A generator earns its keep when it *transforms* (Codex strips frontmatter
keys). Here the transformation is the identity function, so it would be a
build step whose only output is a copy — more moving parts guarding less.

### Alternative 4 (problem 2): Fix the documentation instead of the code

**Description**: Leave `update-framework` skipping missing targets and amend
`STRAYMARK.md` to say `straymark repair` is required after a new agent lands.

**Pros**:
- Smallest diff; no behavior change for existing installations.

**Cons**:
- Makes every future agent surface depend on adopters reading a release note
and running a second command. The failure is silent: the adopter's Qwen Code
keeps starting with no governance and nothing reports it.
- The documented contract is the better one. Choosing the worse behavior to
match a stale sentence inverts the relationship between intent and code.

## Decision

**Chosen**: Alternative 1 for the channel shape, and *fix the code* (rejecting
Alternative 4) for the update path.

**Justification**: The mirror matches both the runtime's actual capabilities
and the precedent set for Qoder, and the parity test plus the new CI job make
the duplication self-correcting rather than a maintenance liability. On the
update path, `STRAYMARK.md` already described the behavior adopters need; the
code was the outlier. Creating missing targets during `update-framework` is
also what makes this release meaningful for existing installations rather than
for new ones only — the exact question that prompted the review.

## Consequences

### Positive
- Qwen Code users receive the governance pointer (`QWEN.md`) and all 15 skills.
- Any *future* agent surface now reaches existing installations through the
ordinary `straymark update`, with no second command and no release note to
read.
- The Qoder and Qwen mirrors are gated in CI; previously only `.codex` was, and
the Qoder parity test lived in a suite the pipeline never ran.
- `validate --agent` stopped being Codex-shaped and is now per-agent.

### Negative
- Six directive files are now written into an adopter's project root at `init`
(`AGENTS.md`, `CLAUDE.md`, `GEMINI.md`, `QWEN.md`, plus the two Cursor files).
Root clutter grows with each supported CLI, and there is still no opt-out
short of editing the manifest.
- `dist/` carries a third byte-identical copy of the skill corpus.

### Risks
- **A future release could now overwrite a file the operator had deleted on
purpose.** Deleting `QWEN.md` no longer survives `straymark update` — it is
restored. Mitigation: this is the documented contract (`STRAYMARK.md` §
"Directive Injection Markers": "There is no opt-out per target short of
editing the manifest"), and it matches what `repair` has always done. If
adopter feedback shows people deleting directive files deliberately, the
right fix is a per-target opt-out in `config.yml`, not re-introducing the
skip.
- **Qwen Code's default context filename could change.** It is a fallback in
the resolver, not a constant an adopter can rely on forever. Mitigation:
`AGENTS.md` is also shipped, so a runtime that adopts the open standard
degrades to covered rather than uncovered.
14 changes: 12 additions & 2 deletions .straymark/follow-ups-backlog.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
last_scan: 2026-08-04
last_scan: 2026-08-05
schema_version: v1
total_open: 1
total_open: 2
total_promoted: 0
total_closed_in_session: 0
total_phase_blocked: 0
Expand All @@ -14,6 +14,7 @@ buckets:
- operational
fully_extracted_ailogs:
- AILOG-2026-08-04-003
- AILOG-2026-08-05-003
---

# Follow-ups Backlog
Expand Down Expand Up @@ -49,6 +50,15 @@ Entry shape (v1 — optional fields marked):
- **Cost**: TBD
- **Notes**: Auto-appended by `straymark followups drift --apply` 2026-08-04.

### FU-002 — Verify `QWEN.md` and `.qwen/skills/` land through a real `straymark init` and a real `straymark update` once…
- **Origin**: AILOG-2026-08-05-003 §Follow-ups
- **Source-hash**: db86ed449fc7
- **Status**: open
- **Trigger**: TBD
- **Destination**: TBD
- **Cost**: TBD
- **Notes**: Auto-appended by `straymark followups drift --apply` 2026-08-05.

## Bucket: time-triggered

## Bucket: charter-triggered
Expand Down
Loading