Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
b5272d1
feat(core): add mds-variants output name and dir validation
dean0x Sep 9, 2026
a2ed82f
refactor(build): teach build-mds the generator-host convention
dean0x Sep 9, 2026
ef0b30f
feat(core): resolve agents dist-first with a src fallback
dean0x Sep 9, 2026
e768675
refactor(git-agent): convert git.md to an MDS generator host, byte-id…
dean0x Sep 9, 2026
f218c00
test: restore git-agent coverage in three degraded corpus scans
dean0x Sep 9, 2026
8b99dc1
test: replace MDS count literals with a shared name manifest
dean0x Sep 9, 2026
cc95210
test: guard dist/agents as a shipping artifact directory
dean0x Sep 9, 2026
cf09961
test: pin the MDS compiler and the tarball's generator sources
dean0x Sep 9, 2026
596233d
docs: record the compiled-agent build in the repo's prose
dean0x Sep 9, 2026
6045b85
docs: retire the last stale src/assets/agents/git.md references
dean0x Sep 9, 2026
3fe8aa6
refactor: simplify Phase 1 build plumbing and guards
dean0x Sep 9, 2026
4a6d987
fix(tests): make the excluded integration test reachable, correct its…
dean0x Sep 9, 2026
00f2d02
docs: align knowledge bases and changelog with the compiled-agent build
dean0x Sep 9, 2026
fa728cc
docs(knowledge): correct MDS discovery scope and generator-host strip…
dean0x Sep 9, 2026
3bf9ed0
docs(knowledge): update feature-knowledge-system feature knowledge base
dean0x Sep 9, 2026
7dd9698
fix(build): give the MDS host variant one owner, aggregate every refusal
dean0x Sep 9, 2026
e054f04
fix(build): close the MDS build's three silent-corruption paths
dean0x Sep 9, 2026
7a5d284
fix(build): bound the MDS walk and correct its stale messages
dean0x Sep 9, 2026
6a3bb2f
refactor(tests): give the frontmatter split one owner
dean0x Sep 9, 2026
677fb98
test(build): stop the generator-host tests rewriting the real dist/
dean0x Sep 9, 2026
e1a695a
refactor(agents): give the dist-first resolution order one owner
dean0x Sep 9, 2026
2f471cd
fix(agents): report a registry agent with no shipped default
dean0x Sep 9, 2026
ece15ce
fix(build): prune unclaimed artifacts from dist/agents/
dean0x Sep 9, 2026
fa20062
docs: describe the hand-authored vs generator-host agent split
dean0x Sep 9, 2026
5ae0509
fix(tests): require an explicit affirmative to run the live-claude test
dean0x Sep 9, 2026
506c85b
refactor(tests): make the _partials flatness guard recursive, name th…
dean0x Sep 9, 2026
db46e23
test(guards): make four green tests able to go red
dean0x Sep 9, 2026
9d2c941
test(build): stop build-mds.test.ts rewriting the real dist/
dean0x Sep 9, 2026
070ceb7
refactor(tests): drop the duplicated build helpers from the generator…
dean0x Sep 9, 2026
6ab72e0
docs(knowledge): correct agent resolution and give the count rule one…
dean0x Sep 9, 2026
11adebf
refactor: align outputNameRefusal wording and writeAgentFile naming
dean0x Sep 9, 2026
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
4 changes: 2 additions & 2 deletions .devflow/features/compliance-feature/KNOWLEDGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ directories:
- src/cli/commands/compliance.ts
- src/assets/skills/compliance
- src/assets/rules/compliance.md
- src/assets/agents/git.md
- src/assets/agents/git.mds
- src/assets/commands/code-review.mds
- src/assets/commands/plan.mds
- src/assets/commands/implement.mds
Expand Down Expand Up @@ -303,7 +303,7 @@ Collects the commit list (≤100 entries) and shipped issue numbers (≤50) sinc
| `src/core/plugins.ts` | `FEATURE_OWNED_SKILLS`, `FEATURE_OWNED_RULES`, `DELETED_PLUGIN_NAMES`, `resolveFeatureRedirect` |
| `src/cli/commands/rules.ts` | `seedRuleShadow` (Tier 1 skipped for FEATURE_OWNED_RULES; Tier 2 = canonical source preserves placeholder) |
| `src/assets/commands/_partials/_compliance.mds` | `compliance_gate()` partial — single-source COMPLIANCE_SKILL_INSTALLED resolution for all 4 host commands |
| `src/assets/agents/git.md` | All traceability operations (D1–D9 legend, D4 rate-limit backpressure, D9 gate table, gather-release-evidence, setup-task containment, Principle 8 marker neutralisation) |
| `src/assets/agents/git.mds` (compiles to `dist/agents/git.md`) | All traceability operations (D1–D9 legend, D4 rate-limit backpressure, D9 gate table, gather-release-evidence, setup-task containment, Principle 8 marker neutralisation) |
| `src/assets/commands/code-review.mds` | Step 0b (imports compliance_gate), Phase 1 regulated-surface gate, Git COMPLIANCE field |
| `src/assets/commands/resolve.mds` | Phase 1b (fetch-review-threads), Phase 9b (resolve-review-threads), Phase 9c (check-merge-readiness) |
| `src/assets/commands/plan.mds` | compliance_gate gate for compliance Design agent and mandatory issue linking |
Expand Down
19 changes: 16 additions & 3 deletions .devflow/features/dynamic-workflow-engine/KNOWLEDGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ directories:
- dist/commands
- tests/build-mds.test.ts
created: 2026-07-07
updated: 2026-08-22
updated: 2026-09-09
---

# Dynamic Workflow Engine
Expand Down Expand Up @@ -68,7 +68,19 @@ Partials declare **no** `output-dir:` frontmatter key. Host files declare it as

### Compiled output and test pinning

`scripts/build-mds.ts` compiles all 13 host files (9 knowledge + 4 dynamic) — `ALL_HOSTS = 13`. **`DIST_FILES` = 14**: the 13 compiled outputs plus `release.md`, which is hand-authored and copied verbatim by the build; the divergence is permanent (SG-13). Compilation-scope guards use `ALL_HOSTS`; deployed-behaviour guards (gh-issue scope, compliance_gate, retired wording) use `DIST_FILES`. The test file `tests/build-mds.test.ts` reads the compiled `dist/commands/dynamic-build.md` and greps for exact doctrine strings. Changing a doctrine literal in a partial immediately breaks the relevant test — by design. The test suite pins:
**This KB owns the 13/14/14 count rule.** The `feature-knowledge-system` and `test-harness` KBs point here rather than restating it, so there is one place to correct when a number moves (applies PF-053).

Three numbers, three sets:

| Number | Name | The set it counts | Why it differs from the others |
|--------|------|-------------------|-------------------------------|
| **13** | `COMMAND_HOSTS` (`MDS_COMMAND_HOSTS`) | Command hosts under `src/assets/commands/` — 9 knowledge + 4 dynamic — compiled into `dist/commands/` | Excludes `git.mds`, which is a **generator host**: it declares `output-dir: dist/agents` and compiles to `dist/agents/git.md`, never to `dist/commands/` |
| **14** | `ALL_MDS_HOSTS` | Every host the build discovers and compiles: the 13 command hosts **plus** the `git.mds` generator host | Counts compilation inputs across both destinations |
| **14** | `DIST_FILES` (`DIST_COMMAND_FILES`) | Files that must exist in `dist/commands/` after a build: the 13 compiled command outputs **plus** `release.md` | `release.md` is hand-authored and copied **verbatim** — it is not a host and is never MDS-compiled; the divergence is permanent (SG-13) |

The two 14s are different sets that happen to share a length: one is inputs (both destinations), one is outputs (one destination). Never conflate the three numbers. Compilation-scope guards use `COMMAND_HOSTS`; deployed-behaviour guards (gh-issue scope, compliance_gate, retired wording) use `DIST_FILES`.

Host and partial names are shared across the suite by the manifest at `tests/fixtures/mds-manifest.ts` (`MDS_COMMAND_HOSTS`, `MDS_GENERATOR_HOSTS`, `MDS_PARTIALS`, `ALL_MDS_HOSTS`, `DIST_COMMAND_FILES`, `HAND_AUTHORED_COMMAND_FILES`) rather than by count literals — the manifest answers "which?", so a rename plus an addition in one commit cannot stay green. `COMMAND_HOSTS` is the local alias `tests/build-mds.test.ts` gives `MDS_COMMAND_HOSTS`. The test file `tests/build-mds.test.ts` reads the compiled `dist/commands/dynamic-build.md` and greps for exact doctrine strings. Changing a doctrine literal in a partial immediately breaks the relevant test — by design. The test suite pins:
- `Simplify` and `Scrutinize` each appearing exactly **2 times** (Gate 1 #1 + Gate 1 #2 only)
- **C1 (single-pass review):** presence: `The review pass runs exactly ONCE`, `The pass runs exactly ONCE`, `Never author additional cycles or a delta re-review of fix commits` (invariant #7 unique), `Budget scales roster and verification votes, NEVER the number of passes` (review_pass prose unique); absence: `DELTA REVIEW`, `reviewBaseSha`, `preFixSha`, `maxCycles`, `cyclesRun`, `fixedInCycle`, `allCoverageGaps`, `for (let cycle` (skeleton guard), `review_loop`, `/review[- ]loop/i`
- `reviewed: true`, `coverageGaps.length === 0`, `FAIL-FIXED`, `ALWAYS ready`, `Cheapest-sufficient validation`, `One build gate per phase`, `NEVER wrapped in`, `Gate 1 #2`, `gate1-final`, `No unauthorized GitHub side-effects`
Expand Down Expand Up @@ -275,7 +287,8 @@ In the SINGLE mode workflow's final Gate 1 (#2, `gate1-final` phase), retry atte
- `src/assets/commands/dynamic-build.mds` — main build command source with inline SINGLE + WAVE workflow scripts
- `dist/commands/dynamic-build.md` — compiled artifact pinned by test suite
- `tests/build-mds.test.ts` — doctrine-literal pinning tests (sections 10, 12, 13)
- `scripts/build-mds.ts` — unified MDS compiler (13 compiled hosts `ALL_HOSTS`; `DIST_FILES` = 14 including hand-authored `release.md` — SG-13 permanent divergence)
- `scripts/build-mds.ts` — unified MDS compiler for both host kinds (command hosts → `dist/commands/`, generator hosts → `dist/agents/`); see the count-rule table above for what 13/14/14 each count. The pipeline itself — discovery, destination validation, the frontmatter strips, pruning — is documented in the `feature-knowledge-system` KB
- `tests/fixtures/mds-manifest.ts` — shared name manifest for the suite: `MDS_COMMAND_HOSTS`, `MDS_GENERATOR_HOSTS` (`['git']`), `MDS_PARTIALS`, `HAND_AUTHORED_COMMAND_FILES`, `DIST_COMMAND_FILES`, `ALL_MDS_HOSTS` — tests derive counts from these instead of pinning literals

## Deliberate Exceptions (AC-0.4 gh-issue scope guard)

Expand Down
10 changes: 6 additions & 4 deletions .devflow/features/external-model-routing/KNOWLEDGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ The old "override confirm" prompt is gone. The merge is purely additive (Devflow

## Mapping Engine (agent-models.json)

`~/.devflow/agent-models.json` is a **deviations-only** mapping: agents that use their shipped defaults are omitted entirely. There is **no `previousModel` field** — shipped defaults are read live from `src/assets/agents/` source files at convergence time via `loadShippedDefaults()`.
`~/.devflow/agent-models.json` is a **deviations-only** mapping: agents that use their shipped defaults are omitted entirely. There is **no `previousModel` field** — shipped defaults are read live at convergence time via `loadShippedDefaults()`, which walks `agentSourceDirs()` (`dist/agents/` then `src/assets/agents/`) first-wins. A registry agent that neither directory supplies is reported through the aggregate `onWarning` channel rather than silently defaulting.

**Type precision**: `EFFORT_LEVELS` in `agent-models.ts` and `CLAUDE_MODEL_ALIASES` in `external-models.ts` are both `as const`, giving derived literal union types (`EffortLevel = 'low'|'medium'|'high'|'xhigh'|'max'`, `ClaudeModelAlias = 'haiku'|'sonnet'|'opus'|'fable'`). These flow through `AgentMapping.effort`, `EffectiveConfig.effort`, and `AgentRow.configuredEffort`/`originalEffort` as `EffortLevel` (not `string`). The one remaining `as EffortLevel` cast at the `readAgentMapping` parse site is sound: the `has()` check proves membership before the cast.

Expand Down Expand Up @@ -314,8 +314,10 @@ Effort is orthogonal to dormancy — it always applies regardless of proxy state
### `loadShippedDefaults` and `reapplyAgentMapping` — parallel execution

Both use `Promise.all` for parallel I/O:
- `loadShippedDefaults()` reads all agent `.md` files from `agentsDir()` concurrently.
- `reapplyAgentMapping()` processes all agent files concurrently via `Promise.all` over the agent name list.
- `loadShippedDefaults(dirs = agentSourceDirs(), opts?)` reads every agent `.md` in each directory of `agentSourceDirs()` concurrently (`readDirDefaults` per directory) and merges them **first-wins**, so `dist/agents/` supersedes `src/assets/agents/` for a name present in both. A missing directory on either side yields an empty map.
- `reapplyAgentMapping()` processes all agent files concurrently via `Promise.all` over the agent name list. It accepts an optional `agentSourceDirs` (same convention, injectable for tests) and passes its own warning channel down to `loadShippedDefaults`; `revertExternalAgents` forwards both.

**Registry-gap warning**: after the merge, `loadShippedDefaults` compares the resolved names against `getAllAgentNames()` and emits ONE aggregate `onWarning` message naming every registry agent no directory supplied, pointing at `npm run build:mds` (mirrors the installer's throw message, which fires on the same invariant). It does not throw — `devflow agents --list` must still render. This is the disclosure for a real silent failure: in a `build:cli`-only tree `dist/agents/` is absent and the generated agent has no `.md` source, so `resolveEffective` returns `model === undefined`, `reapplyAgentMapping` buckets the agent `'unchanged'`, and **disabling the proxy leaves a GPT-pinned agent unreverted** (PF-022). `devflow agents` passes `p.log.warn` as the channel; `reapplyAgentMapping` routes it into `ReapplyResult.warnings`.

Warning collection is **deterministic**: each parallel task returns its local warnings alongside its bucket result; the outer loop aggregates in `allNamesList` insertion order. Warnings are emitted to `opts.onWarning` immediately for live feedback and also collected for the returned `ReapplyResult.warnings` array.

Expand Down Expand Up @@ -395,7 +397,7 @@ A user who hardened `settings.json` to `0600` (to protect `ANTHROPIC_API_KEY`) n
- **Short-circuiting the disable settings pass with `||`**: `removeProxyHooks(s) || _stripProxyEnvFromObject(s, port)` leaves `ANTHROPIC_BASE_URL` set when hooks are present. Both operations must run unconditionally — see `applyDisableToSettings`.
- **Running `reapplyAgentMapping` before proxy preflight completes**: preflight can force `proxyEnabled=false`, and the dormancy logic depends on the final resolved value. In init, the guard is placed immediately after the proxy preflight block.
- **Calling `process.exit()` inside a finally-guarded scope in the TUI**: cleanup must be wired via Promise `resolve()`. Any `process.exit()` inside `finally` terminates without running cleanup and causes event-loop issues (avoids PF-014).
- **Using previousModel in agent-models.json**: The mapping has no `previousModel` field. Shipped defaults are always read live from `agentsDir()` source files. Caching a previousModel creates stale drift when source agent files are updated.
- **Using previousModel in agent-models.json**: The mapping has no `previousModel` field. Shipped defaults are always read live from the directories `agentSourceDirs()` names — `compiledAgentsDir()` first, `agentsDir()` as the fallback — so a generator host's compiled artifact is the default for the agents it produces. Caching a previousModel creates stale drift when those agent files are updated.
- **Duplicating the dormancy predicate**: `isDormantExternalModel(model, proxyEnabled)` from `external-models.ts` is the single source of truth. Do not inline `!isClaudeModelName(model) && !proxyEnabled` at call sites.
- **Pre-spawn doctor gating (chicken-and-egg)**: The relay's `doctor` subcommand probes the relay port to confirm it is running — a not-yet-started relay makes that probe fail (exit 1). A pre-spawn gate is therefore always unsatisfiable on a cold path and invisible to unit tests that mock doctor exit 0 (found during the first live enable). Doctor must gate post-spawn only, after the relay is confirmed up (D-EFR-2).
- **D-EFR-3: Never mock the routing-runtime subprocess without a paired real-binary test**: any test that mocks the routing-runtime subprocess must be paired with at least one CI-executed test that does not. The specific trap (PF-016 reproduced exactly): `tests/integration/**` is excluded from `npm test` by `vitest.config.ts` while CI runs only `npm run build && npm test` — a real-binary test placed in `tests/integration/` would never execute in CI. Place real-binary tests in `tests/` (not `tests/integration/`).
Expand Down
Loading