# Changelog

All notable changes to `stackone-agent-harness` are documented here. Format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions track
`.claude-plugin/plugin.json`.

> **Note:** entries prior to 0.6.0 were removed — they described an earlier 8-phase
> design that never applied to this plugin. This file restarts at the current state.

## [Unreleased]

### Removed — the `build-prereq-graph` skill

It told the agent to import `buildPrerequisiteGraph` from `src/connectors/prereq-graph.ts`, a path
that does not exist in a synced connector project, and nothing in the package exported it. The
orchestrator phase that called it was removed in #358. `/connector:build` no longer mentions it as an
optional step.

### Added — the changelog is mandatory on every generated connector (N23 + V10)

Every published version must record what changed — the initial `0.1.0` included
("Initial release covering X" is a real record). One shared contract
(`scripts/lib/changelog.js`) enforced at three points so it cannot drift:

- **N23 (build gate, blocking)**: `info.changelog` with a 1–2 line customer-facing
  `summary` (overwritten each bump, never appended); optional `labels`, each one of
  `auth`, `downstream-change`, `bug-fix`, `new-actions`. Missing blocks the build round;
  a stub/over-length summary or unknown label is major.
- **V10 (validate gate)**: re-checks the same contract as the final backstop, plus
  `releaseStage: ga` on a sub-1.0.0 version is blocking — GA declares a maturity the
  version number contradicts. Semver validity stays with N13.
- **Write-time nudge** (`post-write-yaml` hook): a main file written without its
  changelog is told at the Write, while the file is open, instead of one repair round
  later.
- The main-file template (`build-connector-config.md`) and the synced guide's YAML
  example carry the block with the label vocabulary, so the gates are backstops rather
  than the first mention. Covered by `test/gates/validate-changelog.spec.ts` (V10 + N23)
  and the hook spec's `changelogNudge` cases.

### Fixed — fetchers are awaited: foreground spawns, not background (azure transcript forensics)

Transcript analysis showed the fan-out finally firing — and delivering nothing: both
fetchers were spawned in background mode, returned agentIds in ~10ms, and their finished
summaries (3 minutes of work) sat uncollected for 17 minutes while the parent re-did the
extraction inline. Zero SendMessage calls in either run. The missing piece was never a
timeout — it was the await.

- **Every spawn site now mandates FOREGROUND (`run_in_background: false`), batched in one
  message**: research fetchers, validate's schema-reviewer + comparators, and the
  worker-agents rule in `/connector:full`. Foreground calls in one message still run
  concurrently; the message returns with every summary in hand, making collection
  structural instead of an advisory obligation the model demonstrably drops.
- **Independent work moves before the spawn** (auth investigation, provider-level facts,
  the discovery poll) — the overlap survives, reordered, without depending on async
  collection.
- **`set-scope`'s stdin-parse failure now says what it received**: the same transcripts
  show 7 set-scope invocations with zero successful replies — the fanout directive never
  printed because the calls died parsing stdin. The error now includes a 120-char preview
  and the expected pipe shape, so the first failure is diagnosable instead of the eighth.
- The init `fanoutPlan` and set-scope `fanout.directive` strings carry the foreground
  requirement too.

### Fixed — fan-out contract planted at init (azure twin-run analysis, third occurrence)

Two more azure runs (commit 4e1b26c — one commit before the set-scope directive landed)
went fully serial: zero subagents in 117 and 162 parent messages, one run killed at 98%
of cap. The same analysis showed run 2 executing Validate with zero subagents despite
`connector-validate.md` instructing delegation outright — evidence that command-file
instructions may not reach the sandboxed model at all, and only in-band signals (tool
results, the synced guide) reliably do.

- **`run.js init` now returns `fanoutPlan`**: init is the one script every gated run
  provably executes first, so its reply is the earliest in-band moment to plant the
  contract — scope thresholds, the fetcher fan-out obligation, and the pointer to
  set-scope's `fanout.required` confirmation.
- The decisive test is the next benchmark on ≥49c89f5: if azure still extracts serially
  with `fanout.required: true` in its own tool results, the block is subagent
  availability in the harness, not instruction adherence — escalate to ai-benchmarks,
  not more plugin text.

### Fixed — fan-out enforced at decision time, not doc time (azure serial-research repeat)

An azure run on a commit that already carried the MANDATORY ≥2-resources fan-out rule
still researched serially (19 one-per-turn doc fetches, ~649s research) — instruction
text read minutes earlier lost to the in-the-moment default. Escalations:

- **`set-scope` now returns a `fanout` directive** computed from the scope it just wrote
  (`required: true` at ≥2 resources or >8 selected endpoints, with the spawn instruction
  inline). The reply lands in the tool result at the exact moment the extraction decision
  is made — the strongest lever short of a harness change.
- **Fetchers prefetch in batches**: a fetcher's slice pages are independent — fetch ~5
  per message, then run the per-endpoint workflow (soft-404 and coverage gates still per
  page). Same tokens, most of the 19-round-trip tax gone; also applies to legitimate
  inline extraction below the threshold.
- **Startup discipline** in `/connector:full`: never read `agents/*.md` (definitions load
  into the spawned agent; spawning needs only the name — the new worker-agents table must
  not induce reading four files at startup), first Bash call is `run.js init`, reference
  reads batched while slow work runs. A run measured 247s before its first gate; reading
  before launching anything is the one startup cost nothing overlaps.

### Changed — 2–4 minutes of wall-clock recovered per multi-resource build, token-neutral

Three levers, all overlap or turn-batching — the same calls happen either way, so cost is
unchanged (slightly down where turns disappear):

- **The fetcher wait is work time**: while `resource-endpoint-fetcher` agents run (1–3
  min), the parent does auth investigation, rate limits, error envelope, and the
  `discover_actions` poll — all independent of endpoint extraction by design. Stated in
  the research command and skill.
- **Validate spawns both halves concurrently**: `schema-reviewer` plus one
  `provider-comparator` per resource go out in one message (comparators batched 3–5);
  fixes happen after the summaries return. Sequential passes serialized 1–2 min of doc
  re-fetching per resource for no ordering benefit.
- **Fewer turns**: a repair round is ONE `phase.js` call (auth→build→conventions
  combined), not three gate calls; a passing gate chains `&& set-phase` in the same Bash
  call (exhausted transitions, which follow a failing round, stay standalone);
  independent partials are written 2–3 per message. Each avoided turn is a round-trip
  saved and one less context replay.

### Fixed — nondeterministic research fan-out (freshservice twin-run divergence)

Two runs on the same prompt, commit and environment diverged: run 1 extracted 13
endpoints sequentially and was killed at the 25-minute cap; run 2 fanned out to subagents
and finished in 854s. Root cause: a direct contradiction — `connector-full.md` banned
spawning "for any phase" while `resource-endpoint-fetcher` exists to be spawned in
parallel, and the step that was supposed to spawn it ("Phase 4" of `/connector:research`)
did not exist in the command. Each run resolved the conflict differently.

- **The ban now names its carve-out**: never delegate the run or a whole phase; the four
  scoped worker agents (`resource-endpoint-fetcher`, `use-case-discoverer`,
  `schema-reviewer`, `provider-comparator`) are part of the flow, listed in
  `connector-full.md` with when each is spawned.
- **`/connector:research` gained the missing step**: after scope, spawn one fetcher per
  in-scope resource in batches of 3–5 — **mandatory at ≥2 resources or >8 in-scope
  endpoints**, with a mid-course rule (fetching inline with >8 endpoints left → stop and
  fan out). Stated in the command, the research skill, and the synced guide.
- **Stale phase references fixed**: agent and reference docs pointed at "Phase 4" /
  "Phase 2D" / "Phase 4C" steps that no command defines; all now use rot-proof wording.

### Changed — modeling decisions move to research time (second azureblobstorage autopsy)

The follow-up run's time went to modeling mistakes the gates caught late: transport
headers exposed as inputs, re-pluralized partial filenames, `method: head`, and a
mid-build stall deciding how to send XML bodies.

- **`transport: true` on researched parameters**: auth/runtime-managed headers
  (`Authorization`, `Date`/`x-ms-date`, `Content-Length`, `Host`, fixed version pins like
  `x-ms-version`) are recorded for evidence but never demanded as inputs — B4 skips them,
  mirroring the existing `legacy` flag. The fetch skill, output schema example, and gate
  schema all carry the classification; fixed pins become literal step args at build.
- **Partial resource segment is research's resource key, verbatim** — stated in the build
  skill: B2 matches by exact string, so a re-pluralized `containers` partial fails against
  the researched `container`.
- **`method` enum documented**: `get post put patch delete` only — all 29,789 corpus
  steps; the CLI rejects `head`. HEAD-documented endpoints are modeled `get` or dropped
  with a reason. In the build skill's step section and the tricky-handling traps table.
- **REST XML bodies get a pattern**: new tricky-handling section — raw XML string input
  sent as the body with explicit `Content-Type` (pilot `spread: true`; `upload_binary`'s
  `content:` is the verified raw-body fallback), with the shape decided at research time.
- **Pilot the HARDEST action first**, not the first one written — an XML/upload/odd-method
  action surfaces the open design questions in round 1; a plain list validates while every
  question waits in the batch.
- **Fetch batching**: five per batch is a floor — raise it after two clean batches; ask
  each fetch for a narrow extract (and budget for it being ignored on boilerplate-heavy
  hosts like Azure's).

### Changed — every gate rule is a pre-write read (azureblobstorage 2x-cost autopsy)

An azureblobstorage run took double time; the autopsy traced it to rules that existed only
in gate code, docs that drifted from the CLI, and a slug convention discovered mid-build.

- **N13–N22 documented**: `naming-conventions.md` covered N1–N12 while the gate enforced
  through N22 — B4's literal-name matching and N21's no-query-string rule each surfaced
  only as a post-write gate failure and each cost a full rewrite of both 900-line partials.
  The reference, the build skill's rule table, and the gate's own `--schema` metadata
  (which was also missing N21/N22) now all carry the full set.
- **Input names are literal wire names**: stated in the build skill and the guide — B4/B5
  match researched parameters by exact name (`x-ms-lease-id`, never `leaseId`), with N2's
  bracket rule as the one documented exception.
- **Pilot-action step**: the build skill now runs the first written action through
  `build.js --only B4,B5,B10` and `conventions.js` before the remaining actions are
  written — a shape mistake becomes a 30-line fix instead of a 2,000-line rewrite.
- **`run.js init` rejects non-flattened provider slugs** and names the corrected token
  (`azure-blob-storage` → "re-run with --provider azureblobstorage"). One cheap retry at
  init replaces re-running init, scope, and the research gate when the filename convention
  surfaces mid-build.
- **The CLI outranks the corpus docs**: `authentication.md` and the build skill now say a
  `stackone validate` error wins over any shape shown in the references — fix to the error
  and record the drift, never reverse-engineer the schema from `node_modules` bundles.
- **Build-skill input examples fixed**: the Input Types and CRUD pattern examples declared
  inputs with `- key:` — the exact shape three of the skill's own warnings call the most
  common build error. All now use `- name:`.

### Changed — scripts run with zero dependencies in any environment (benchmark tool-failure sweep)

Eight benchmark runs surfaced 80 tool-call failures, most of them environment noise, not
logic: `Cannot find module 'js-yaml'` (no npm install in the synced sandbox),
`python3: command not found` (model improvising a runtime the sandbox lacks),
`Cannot find module '/scripts/state/run.js'` (unset `${CLAUDE_PLUGIN_ROOT}` expanding to
empty), and `EISDIR` reads of skill directories.

- **`js-yaml` is now vendored** (`scripts/lib/vendor/js-yaml.cjs`, MIT, v4.3.1): all
  scripts require it through `scripts/lib/yaml.js`, which prefers an installed copy and
  falls back to the vendored bundle. The scripts run with plain Node wherever the plugin
  directory is copied — no npm, no network, no SessionStart install.
- **SessionStart hook no longer runs `npm ci`**; the plugin `package.json` has no
  dependencies left to install.
- **`connector-guide.md` gained a "resolve the scripts root once" preamble**: guard
  `${CLAUDE_PLUGIN_ROOT}`, fall back to `STACKONE_PLUGIN_ROOT` then a filesystem search,
  substitute the resolved root wherever skills show the literal variable, and a
  script-free fallback when `scripts/` was not synced at all. Also states that python is
  unavailable (use `node -e`) and that skills are read via their `SKILL.md`, not the
  directory.

### Changed — explicit scope policy settled (from the freshservice run-06/run-08 pair)

- **Named resources build their full surface, uncapped** — policy set by the user
  2026-08-23 and implemented in `scope.js` (`explicitScope`, `cap: null`). An earlier
  iteration capped explicit mode at 15 endpoints (25 with closure) with an `--uncapped`
  escape; that cap was removed in the same day's follow-up commit, and this entry
  previously described the removed behavior. Naming a resource bounds *breadth* (which
  resources), never *depth* (how much of each); the operations filter is the only
  narrowing that applies.
- **V4 reconciles against `dropped[]`**: an omission validate finds that matches a scope
  drop (any reason) is acknowledged in the pass detail, never blocking — a run can no
  longer fail its own scope decision at validate.

### Added — run-08 waste-ledger eliminators

- **`resolved` field on comparison discrepancies**: set `resolved: "<what changed>"` when
  a discrepancy is fixed; V3/V6 ignore it and the audit record survives. Fourth measured
  occurrence of the delete-on-fix instruction losing to the documentation instinct; a
  bare `resolved: true` still counts as open.
- **autofix `B15`**: inserts `rateLimit.mainRatelimit` from research when research
  documents exactly one unambiguous requests-per-second value and the main file has no
  rateLimit block.
- **research skill**: R8's only-LIST-and-CREATE-originate-ids rule stated at
  endpoint-selection time, not discovered from the gate.
- **build skill**: read all of a loop's reference files in one message — five
  one-per-turn reads paid a full context replay each for no ordering benefit.

### Added — fetch dedup and a redirect probe (from a measured $10.77 slack run)

- **`hooks/post-fetch-dedup.js` (PostToolUse, WebFetch)** keeps a session-keyed ledger of
  fetched URLs (`.connector-dev/.fetch-ledger.json`) and warns on stderr when a URL is
  re-fetched with its content already in context. Restored from the `baseline-5.29` tag.
- **research skill**: probe one URL on an unfetched doc host before batching against it —
  a measured run spent five calls on a host that 302-redirected every one of them.

### Added — repair-round eliminators (each one a measured gate round)

- **V4 `outOfScope[]`**: comparison files gain an `outOfScope[]` bucket (`reason`
  mandatory) for documented endpoints deliberately excluded from scope; entries without a
  reason — and everything in `missingFromConnector` — still block.
- **V8 no longer lags a round**: the runner exposes `ctx.roundResults` /
  `ctx.roundReportedGaps` to checks in the same pass, so V8 sees gaps resolved by this
  round's own checks instead of failing once against the stale ledger.
- **R11 matches by containment**: a prose label from the provider's page ("OAuth 2.0 bot
  token") now matches the mechanism type it contains ("oauth2_bot_token") instead of
  requiring exact string equality.
- **A9/B6 fix text names the map-key rename**: the auth block's YAML map key is its
  matched identifier; the gate now says so and shows the exact rename instead of only
  "not configured". The auth reference templates carry the same callout.
- **autofix `CLI`**: quotes a numeric-looking `placeholder:` value before YAML coerces it
  to a float and `stackone validate` rejects it.
- **autofix `B6`**: renames the sole auth block's map key to the sole researched mechanism
  type when they do not match (plus matching `authRequirements` keys) — fully determined,
  so no model turn is spent on it.
- **`paramCarryOverWarning` in `hooks/post-write-yaml.js`**: diffs a just-written
  partial's inputs against the research artifact's parameter list and warns while the file
  is still open — B4's condition checked at the write instead of a gate round later.

### Added — arena run-01 eliminators (from the first isolated-workspace run)

- **R12 endpoint enumeration**: research must name the provider's method-index page and
  account for every endpoint on it (`endpointEnumeration`, same shape as
  `authEnumeration`). The measured miss it refuses: 12 hand-picked endpoints researched,
  11 more discovered only by validate's live re-derivation — a mid-validate rewind worth
  ~24 of 113 tool calls.
- **R13 lowercase endpoint ids**: `endpoints[].id` must match `^[a-z][a-z0-9_]*$` at
  research time, with the exact rename in the fix text — ids become actionIds verbatim,
  and conventions gate N1 only rejects embedded capitals one phase too late.
- **autofix `REF`**: unquotes a quoted `$ref:` value — the `stackone` CLI's raw
  `':'`-splitting loader cannot strip quotes and fails with ENOENT.
- **validate.js fails fast** on a missing or nonexistent `--connector-path` instead of
  running V1/V2/V7 against a connector that was never loaded.
- **validate skill**: `discrepancies[]` documented as live state, not a changelog — fix
  the YAML and its entry in the same edit, or V3 reads the stale entry as still open.

### Added — prompt routing to the gated flow

- **`hooks/prompt-router.js` (UserPromptSubmit)** routes a connector request arriving as
  plain natural language — not a slash command — into the gated flow.
- **`skills/connector-flow`** is the entry point that router hands off to, registered in
  `.claude-plugin/plugin.json`.
- **`session-start.sh`** gained a line pointing a fresh session at the connector-routing
  skill.
- **`freestyleAlarm` in `hooks/post-write-yaml.js`** (PostToolUse) fires when connector
  YAML is `Write`-n with no run state discoverable nearby — a session bypassing the gated
  flow and authoring by hand — and prints a warning to stderr without blocking the call.

### Removed — three orphaned helper scripts

`scripts/list-accounts.js`, `scripts/list-profiles.js` and `scripts/show-warnings.sh` had
no remaining caller; deleted along with their doc listings.

## [0.7.0] — 2026-08-18

Fixes found by reading the benchmark artifacts for PR #340, where the harness scored 56.3%
against a 69.1% baseline. The single largest cause was not connector quality: three of eight
runs ended their turn at a phase boundary and shipped no connector at all.

### Fixed — a run can no longer stop mid-flow

- `run.json` gains **`targetPhase`**, recording how far a run is meant to go. Set to
  `research` by `/connector:research`; left at `validate` by the full flow.
- The **Stop hook now blocks** (exit 2) while any in-target phase is still owed, instead of
  printing a banner and letting the turn end. Guarded by `stop_hook_active` so it cannot
  loop. A research-only run is unaffected — it has nothing left to owe.
- **`set-phase` replies with `nextPhase` and a directive** rather than a bare
  `{"status":"complete"}`, which read like a finish line at exactly the moment a run was
  most likely to stop.
- `research-provider`, `build-connector`, `/connector:full` and `/connector:research` now
  say that a passing gate is a handover, not an ending. The research skill's
  "❌ Building anything — that is the next phase" was actively inviting the stop and has
  been narrowed to what it meant: do not write YAML before the gate passes.

### Added — `scripts/gates/phase.js`

Runs every gate a phase owns in one invocation (Build's three in order) and reports only
what failed. Each gate still runs as its own process, so ledger write-back and exit codes
are unchanged. Repair rounds are the normal case, and three Bash calls per round is real
time: runs averaged 47 Bash calls each, and one was killed at 902s having never written a
connector.

### Added — operations as a scope axis

`scope.js` takes **`--operations`**, for requests that restrict which operations to build
("actions for *searching* people and organizations"). `search`, `read`, `get`, `create`,
`update`, `delete`, `write` and `manage` map onto the research `operation` values; `search`
resolves to `list`, because a provider's search endpoint is its collection reader whatever
verb it is documented under. Excluded endpoints are recorded as `operation-out-of-scope`,
never discarded.

Without it, an Apollo run asked for "searching people and organizations" built enrichment,
bulk-enrichment and job-posting actions on top — 7 selected endpoints where 2 were asked
for. With it, 3.

### Removed — no hook may refuse a tool call

Three checks could stop an unattended run. All are gone.

**`guard-bash.js` (PreToolUse, deleted).** It refused `rm` on any path under `src/`,
`node_modules/` or `.connector-dev/`, and it was wrong in both directions. It blocked
`rm src/configs/<p>/<stale>.s1.partial.yaml` — the only remedy for a **blocking** `B8`
failure on a partial that exists but is not `$ref`'d, since emptying the file leaves it in
the file list and adding a `$ref` would ship a resource nobody asked for. And it stopped
nothing determined: a leading `./` walked straight past it, so `rm -rf ./src`,
`rm -rf ./node_modules` and `rm -rf ./.connector-dev` were all permitted while their
un-prefixed forms were refused. Obstruction without protection.

**`guard-run-json.js` (PreToolUse, deleted).** It refused hand-edits of `run.json`. The rule
it enforced is right — completion must stay computed — but it is now carried by the phase
instructions, which already say it. A hook that can refuse a write can also stall a run.

**`rm` / `rmdir` / `unlink` in `src/hooks/safety.ts`.** `/\brm\s+/` matched *every* delete
and answered `"Please ask the user for confirmation before proceeding."` In an unattended
run there is nobody to ask, so it did not make the run safer — it ended it. The six
genuinely catastrophic patterns stay (fork bomb, `mkfs`, `dd` to a device, writes to
`/dev/`, `rm --no-preserve-root`, piping remote content to a shell); none is anything a
connector build issues.

`hooks.json` now registers SessionStart, PostToolUse, Stop and PreCompact — **no
PreToolUse**. `test/gates/command-wiring.spec.ts` asserts that, and that no `guard-*`
script returns.

**The Stop hook stays, deliberately.** It is the one hook that still exits 2, and it blocks
the turn from *ending* while a phase is still owed — it keeps a run going rather than
halting it. Removing it would restore the failure that made this work necessary: a run that
finishes research, narrates the milestone and ends the turn, shipping no connector.

### Fixed — nothing in the flow can pause or dead-end a run

An audit of every mechanism able to halt Research → Build → Validate found two.

**`/connector:research` restricted itself out of doing its own job.** It was the only command
declaring `allowed-tools`, and the list — `WebSearch`, `Bash(mkdir*)`, `Bash(node*)` — omitted
`Write` (its own artifact), `Read`, `Skill`, the documentation fetcher and the four MCP tools
its first step calls. Under real permissions it could not produce the research artifact it
exists to produce. It went unnoticed because the benchmark runs with permissions bypassed,
which makes the declaration inert.

Removed rather than expanded: the research tools live on an MCP server whose registration
prefix is environment-dependent, so no tool name the plugin hardcodes stays valid across
hosts, and `allowed-tools` is tool-level anyway — it cannot express the constraint that
matters ("investigation only, never connector YAML"), which is already prose and is enforced
downstream by the build gate reading a different path. The other four commands carry no
restriction; this one was an outlier, not a policy.

**`/connector:build` dead-ended when research was missing.** It halted and told the customer
to run `/connector:research` first — fine interactively, a dead end unattended. It now loads
`research-provider`, drives it to its gate, records the transition and continues into build
in the same turn, initialising the run first when there is none. This matches the rest of the
system: `/connector:full` forbids stopping, `/connector:validate` has no prerequisite stop,
and the Stop hook refuses to end a turn while a phase is owed.

Both are covered by `test/gates/command-wiring.spec.ts`, which asserts no command declares
`allowed-tools` and that the build command resolves a missing prerequisite rather than
stopping.

What remains able to interrupt a run, all deliberate: the two `PreToolUse` guards (refusing
`rm` on load-bearing paths, and hand-edits of `run.json`) are corrective and name the right
move; the Stop hook blocks *ending* a turn, not the work; and `/connector:full` keeps exactly
one sanctioned stop — a provider whose documentation cannot be found at all.

### Changed — research starts its slow work first

Research is dominated by waiting on other people's servers, and two orderings were wasting
most of that wait.

**Action discovery now launches before any documentation is read.** `discover_actions` runs
5–15 minutes server-side and returns a taskId immediately, so starting it first puts that
wait underneath the manual work. It had been firing well into the phase, after most doc
reading was already done — and in at least one observed case it was launched and then never
polled, spending the full wait and collecting nothing. Reading the result before the gate is
now an explicit step, not an implication.

**Independent documentation pages are fetched concurrently.** Once the endpoint list exists,
the pages to read are known up front and none depends on another, so they go out as several
tool calls in one message. Batches of about five are the documented default — that size is
what runs already reached organically, without a single rate-limited fetch. Pages that do
depend on one another (an index before what it links to) stay sequential: fan out within a
level, not across one.

Both were already half-stated — the skill said "launch it first" inside a priority list, and
the `discover-actions` skill said "run in parallel, don't block" — but nothing sequenced
them, so neither held.

### Added — the customer experience is now a contract, not a suggestion

The customer states what they want, and in the common case that is the whole interaction.
Five narrated lines, and nothing else:

```
✓ Found Ashby's API docs — 3 resources, 41 endpoints
✓ Researched candidates, notes and applications
✓ Built the connector — 41 actions, 318 parameters
⟳ Re-checking every action against Ashby's documentation…
✓ Complete — no gaps found
```

`scripts/state/narrate.js` emits them, computed from the research artifact and the connector
YAML — so a narrated "41 actions" is 41 actions, where a recalled one drifts. Verified
against a real fixture: the Slack run narrates 23 actions and 102 parameters, matching the
partials exactly. The completion line stays honest, reporting `2 gaps recorded` or
`validate could not be driven to green` when that is what happened.

What it replaces is the multi-line `✅ Phase 1 Complete: Research Provider / Provider: … /
Artifact: … / Gate: …` block — the same shape the freshservice run printed immediately
before ending its turn with no connector written.

**Zero required interactions.** Nothing deploys, connects an account or calls a live API, so
no credentials, profile, account or consent are needed. Three places still asked, and all
three now derive instead:

| Was asked | Now |
|---|---|
| "Where should the connector files live?" | `src/configs/<provider>/` if it exists, else `<provider>/` — one `test -d` |
| "Does test account have existing data?" | No account is ever connected; pick a LIST endpoint from research |
| Which resources / auth / sandbox | Already had defaults; now stated as a table of resolutions |

The one permitted question is a genuinely ambiguous provider name ("Atlas" → Atlas CRM or
MongoDB Atlas), asked once before `init`, because guessing wrong wastes the whole run and
nothing in the artifact can recover it. A merely unfamiliar name is not ambiguous — research
it. `/connector:test` still confirms before mutating live data; that is a destructive-action
check, not a flow interruption.

### Changed — skills are flat; nested ones became reference docs

Twenty-five skills lived under `skills/build/`, `skills/research/` and `skills/validation/`
and **none of them was ever reachable.** The loader does one `readdirSync` of `skills/` and
keeps entries with a `<name>/SKILL.md`, so anything deeper is invisible — and because sync
copies `skills/<name>/**` recursively, they never reached a sandbox either. The benchmark
runs confirm it: 11 skills synced, all flat. Instructions saying "Load skill:
skills/build/build-actions/action-config" had nothing to load.

They were already marked `user-invocable: false` and `disable-model-invocation: true`, so
reference material is what they always were. They now sit beside the phase skill that uses
them, where sync will actually carry them:

| Was | Now |
|---|---|
| `skills/build/build-actions/*/SKILL.md`, `skills/build/build-connector/*/SKILL.md` | `skills/build-connector/references/*.md` |
| `skills/research/*/SKILL.md` | `skills/research-provider/references/*.md` |
| `skills/validation/*/SKILL.md` | `skills/validate-config/references/*.md` |

Skill frontmatter is stripped — these are documents read by path, not registry entries.
`plugin.json` drops from 35 skill entries to the 10 that were ever real. Three research
scripts built catalog paths from `path.join` segments and were repointed; a text search
would not have found them, so they are verified by running.

`docs/maintenance/plugin-layout.md` now states the rule: invocable → `skills/<name>/SKILL.md`,
one level; technique → `references/` beside its phase skill.

### Added — R11, auth mechanisms enumerated from the provider's auth page

R2 asks whether the mechanisms we recorded are well-formed, which a run that stopped at the
first one passes with a single entry. A Slack build recorded OAuth alone, passed R2, A9 and
B6, and shipped one auth block where three were documented — 32 of 100 on auth with every
gate green.

R11 requires `authEnumeration.docsUrl` plus every mechanism found there with a decision
(`build`, `deprecated`, `not-applicable`) and a reason for each exclusion. It cannot prove
completeness against a page it cannot read; it can refuse the accidental omission by making
it a written decision.

## [0.6.0] — 2026-08-17

Current state of the plugin: a gated three-phase connector flow — **Research → Build →
Validate** — that runs unattended from one request to a terminal outcome
(`complete` or `partial`).

### The flow

- **Five slash commands** — `/connector:full`, `/connector:research`, `/connector:build`,
  `/connector:validate`, `/connector:status`.
- **Four agents** — `research/resource-endpoint-fetcher`, `research/use-case-discoverer`,
  `validation/schema-reviewer`, `validation/provider-comparator`.
- **~35 skills** covering Phase 1 investigation, per-schema-area YAML authoring,
  auth-flow variants, and validation rules.

### Five gates

Phase completion is decided by deterministic scripts under `scripts/gates/`, never by
model judgement. Shared exit-code contract: `0` pass, `1` fail (with gap records), `2`
could not evaluate.

- `research.js` (R1–R10) — full documented surface enumerated, in buildable detail.
- `auth.js` (A1–A12) — auth-block sub-gate of Build, run to green before any action is
  written; A10 requires cited provider auth documentation (`auth-evidence.json`).
- `build.js` (B1–B13) — projection fidelity, measured as built ÷ discovered-in-scope;
  B1 consumes the `stackone validate` verdict via `--cli-ok`.
- `conventions.js` (N1–N13) — corpus-derived conventions mined from the 484 shipped
  connectors (30,726 non-unified actions), each rule stating its adherence rate.
- `validate.js` (V1–V8) — independent re-derivation from the provider's live docs; V2
  rejects empty `docsRead` evidence so the loop cannot certify itself.

### Run state

- All durable state in `.connector-dev/<provider>/run.json`, written only by
  `scripts/state/run.js` (`init` / `set-scope` / `set-phase` / `read` / `summary`);
  hand-edits are blocked by a PreToolUse hook. There is no phase-ledger.json.
- Scope contract via `scripts/state/scope.js` — resources, auth mechanisms, and sandbox
  resolved from the customer's words, with everything not built recorded in `dropped[]`.
- Gap ledger with oscillation detection (a gap that reopens twice becomes
  `not_fixable`), a no-progress rule (two rounds resolving nothing marks the phase
  `exhausted`), and a rewind budget against phase ping-pong.

### Boundaries

- The flow builds and verifies against documentation only — it never deploys, never
  connects accounts, never calls a live provider API.
