feat(skills): skill runtime, instruction resolver, capabilities and support bundle [SC-A0.1 + A0.2a] - #72
Merged
Conversation
PR A0.1 of the SC-A0 Skills & Health integration rescue. Recovers the skill and instruction subsystems from PR #71 (a868f7d) onto current main (41a7e26) without importing PR #71's parallel doctor engine. PR #71 branched at b98ef26 (2026-08-12) and never saw PR #66 (27c100d, 2026-08-14), which landed its own doctor v2 on main. Both declare doctor schema version 2 with incompatible payloads: main's health.ts models configured/reachable/verified as Axis objects with a "not-checked" state, PR #71's contracts.ts models them as plain booleans. A boolean cannot express "not checked", so in fast mode — which performs no network I/O — every remote axis would have to report false, which is indistinguishable from checked-and-failed. main's contract is both newer and safer, so it stays canonical and PR #71's is dropped rather than merged. This commit lands only the part of PR #71 that main has no equivalent of, and which turned out to apply to main unmodified: src/core/skills/* schema, digest, lock, trust, discovery, loader, resolver, policy, bounds, eval, session, settings, context packet, permission vocabulary src/core/instructions/* AGENTS.md discovery and resolution with provenance src/core/why_log.ts capability explanation log Capabilities, support bundle, and the skills/instructions doctor checks follow in A0.2; CLI wiring and packaged skill assets follow in A0.3. The full conflict matrix, including the four textual conflicts and the decision record for each contested surface, is in _loopstate/LOOP-01/sc-a0-2026-08-19/AUDIT-ARTIFACT.md. Gates at this commit, run against this exact tree: npm run typecheck exit 0 npm test 1006 pass / 0 fail (baseline on 41a7e26 was 922/0)
CodeQL js/incomplete-sanitization (high) on PR #72, at src/core/instructions/instruction_resolver.ts:18 — the metacharacter escape in globToRegExp used a non-global regex. Not exploitable as written: `char` is `glob[index]`, always a single code unit, so there is never a second occurrence to miss. But the safety of that escape rests on an invariant nothing in the function states, and a future change to a multi-character token would silently widen every glob's match set. Fixed rather than dismissed, since the fix is free. Adds a regression test that pins the property rather than the implementation: a glob containing . + ( ) | { } [ ] $ ^ must match itself literally and must not match a decoy path, while ** and * keep working. Mutation-checked: replacing the escape with a bare `pattern += char` fails the new test with "unescaped . would match axts"; restoring it passes. Gates at this commit: npm run typecheck exit 0 npm test 1007 pass / 0 fail
PR A0.2 of the SC-A0 Skills & Health integration rescue, part 1 of 2. Recovers PR #71's capability matrix and support bundle onto main: src/core/capabilities.ts capability matrix src/generated/agent_capabilities.ts packaged offline fallback snapshot src/core/support_bundle.ts redacted, self-verifying bundle src/core/redaction.ts shared redaction vocabulary src/core/tar.ts dependency-free tar writer capabilities, redaction, and tar applied unmodified. support_bundle needed a two-line port: it called PR #71's doctorReportV2, which this rescue drops in favour of main's diagnosticReport (see the conflict matrix in _loopstate/LOOP-01/sc-a0-2026-08-19/AUDIT-ARTIFACT.md, decision C1). The swap is a strict improvement for the bundle. PR #71's report modelled configured/reachable/verified as plain booleans, so a fast-mode bundle — which performs no network I/O — had to serialize verified:false for every remote axis, indistinguishable from checked-and-failed. main's HealthReport carries per-axis "not-checked", so an unexercised probe stays visibly unexercised in the artifact a user sends to support. Still owed for A0.2 part 2: porting PR #71's skills and instructions checks (the only two it has that main lacks) from its CheckSpec onto main's CheckOutcome, and the safe-repair reconciliation. Gates at this commit: npm run typecheck exit 0 npm test 1017 pass / 0 fail (1007 before this commit) npm run smoke 3 pass / 3 skip / 1 fail The smoke failure is `cloud turn` HTTP 401 "Invalid or expired session token". Verified pre-existing: the identical failure reproduces at 1521199 with this commit's changes stashed. It is an expired local credential requiring `aether auth login`, not a code regression, and no file in this commit is on the cloud-turn path. Recorded as an operator-owned gate, not a pass. Bundle safety is covered by the ported suites, both passing here: a seeded canary secret is rejected rather than shipped, and an interrupted generation leaves no misleading "complete" artifact behind.
…ontract PR A0.2 of the SC-A0 Skills & Health integration rescue, part 2 of 2. Ports the only two checks PR #71 has that main lacks — skills and instructions — from PR #71's CheckSpec onto main's DiagnosticCheckSpec/CheckOutcome, and wires them into fast mode. Six checks land: skills.index store integrity and index errors skills.lock lockfile presence and drift skills.trust project skills untrusted or changed skills.evals declared eval manifests instructions.graph source count and parse warnings instructions.conflicts detected conflicts, by topic This is a shape translation, not a rewrite. PR #71 modelled a result as a status plus a detail string with configured/reachable/verified as plain booleans; each axis here carries its own state and evidence. The translation is where the contract decision earns its keep. These checks touch only the filesystem, so `reachable` is n/a — there is no remote to reach — rather than a pass borrowed from a probe that never ran. Under PR #71's booleans there was no way to say that: `reachable: false` would have read as unreachable. `verified` is a genuine yes/no because these checks do exercise the files they report on during the run. The axis mapping lives in one helper (`localOutcome`) rather than being repeated per check, so a future check cannot quietly claim a `verified` it did not earn. Extends, rather than relaxes, the frozen check-ID inventory in test/diagnostics.test.ts. That assertion exists to catch unintended drift; this drift is intended, so the expected list grows and the deep-equal stays exact. Adds an assertion that both new categories report `reachable: "na"`, so a later change cannot silently upgrade them to a pass. Live proof against the built CLI at this commit — `aether doctor --json`: skills.index cfg=yes reach=na ver=yes 0 skill(s) indexed skills.lock cfg=yes reach=na ver=yes no project skills, no lock required skills.trust cfg=yes reach=na ver=yes no project skills awaiting trust review skills.evals cfg=yes reach=na ver=yes no skills discovered instructions.graph cfg=yes reach=na ver=yes 0 instruction source(s), no parse warnings instructions.conflicts cfg=yes reach=na ver=yes no instruction conflicts detected The emitted payload contains no boolean-shaped `verified` field, confirming PR #71's competing schema-v2 contract is absent rather than merely unused. "0 skill(s) indexed" is truthful, not a defect: the built-in skill assets are not packaged until A0.3. Gates at this commit: npm run typecheck exit 0 npm test 1017 pass / 0 fail
…ged assets PR A0.3 of the SC-A0 Skills & Health integration rescue. Makes the work in A0.1 and A0.2 reachable. Until this commit the skill runtime existed but no user could invoke it. aether skills <subcommand> list, show, check, trust, lock aether capabilities capability contract, with --available aether support-bundle redacted metadata-only diagnostic archive Built-in skill assets (SKILL.md, skill.json, evals) ship as data, which tsc does not emit, so `build` now runs scripts/copy-skill-assets.js after compile. The existing `files: ["dist/src"]` entry already carries them into the tarball; verified below rather than assumed. Deliberately NOT taken from PR #71: * Its cli_registry entry rewriting `doctor [--deep]` to `[--network] [--fix]`. `--deep` stays read-only per decision C2 in the conflict matrix; aliasing it to a network-performing mode would silently change what an existing habit does. `--network` is a separate, explicitly named mode when it lands. * Its main.ts doctor flags (--network, --schema, --category, --failed), which belong to the doctor engine this rescue drops. main's doctor parses its own argv, so it needs none of them. * Its `aether agent --skill` / `--no-skills` flags and dev-session skill context. Those touch code.ts, chat.ts, brain_cloud.ts and stream.ts, which SC-A1, SC-A4 and SC-A5 own. Deferred rather than raced. COMMANDS.md gains real sections, not just index entries. The parity test only asserts the index, but an index pointing at nothing is a doc that lies by omission. Every subcommand documented was then invoked to confirm it exists: skills list -> 6 built-ins listed skills show -> aether/ship@1.0.0 — Ship skills check -> ok skills trust -> "aether/ship is a builtin skill — already trusted" skills lock -> locked 0 project skills Live proof at this commit, against the built CLI: aether skills list 6 built-in skills, scope and trust shown aether capabilities contract v1, falls back to the packaged snapshot and states that it did so, and why aether support-bundle 25600-byte tar written, sha256 reported aether doctor --json skills.index now reports "6 skill(s) indexed" (it read 0 before the assets were packaged) Gates at this commit: npm run typecheck exit 0 npm test 1017 pass / 0 fail npm run verify:production exit 0 — ok:true, 518 packed files, 2315934 bytes npm pack --dry-run 18 dist/src/skills/builtin/** entries present The COMMANDS.md parity test failed first on this change, correctly: the CLI registry had grown and the doc had not. Fixed by documenting the commands, not by relaxing the assertion.
This file was written into the worktree by running `aether skills lock` while verifying that the documented subcommand actually exists. It is a byproduct of that check, not source, and it was swept in by `git add -A` in the previous commit. `aether skills lock` is a project-scoped command: the lockfile belongs to whichever repository a user runs it in, generated on demand. Shipping this repo's own lockfile would pin an empty project skill set into the package for no reason.
This was referenced Aug 19, 2026
Merged
Merged
…s-health-integration
…s-health-integration
AetherAI3
marked this pull request as ready for review
August 19, 2026 12:42
This was referenced Aug 19, 2026
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
PR #71 ("Skills & Health") is
CONFLICTINGagainstmainand cannot be rebased mechanically. It carries a large amount of genuinely new and valuable work — the skill runtime, the instruction resolver, the capability matrix, the support bundle — but it also carries a second, competing implementation ofaether doctorv2.Root cause
b98ef26d(2026-08-12) is the last commitmainand PR #71 share.mainthen took PR feat: durable media output history, one safe opener, and aether doctor v2 #66 (27c100d8, 2026-08-14 14:42) — "durable media output history, one safe opener, and aether doctor v2".b98ef26d; its last commita868f7d0is 2026-08-14 22:49 — later on the clock, but authored against the 08-12 tree and therefore blind to PR feat: durable media output history, one safe opener, and aether doctor v2 #66.Two independent "doctor v2"s were built in the same 48 hours. That is why only four files conflict textually while the semantic conflict is much larger.
Contract
The contested surface is the doctor report contract. Both sides declare schema version 2 with incompatible payloads:
main—src/core/health.tssrc/core/diagnostics/contracts.tsHealthReport, camelCaseDoctorReportV2, snake_caseconfigured/reachable/verifiedAxisobjects,statein {yes,no,unknown,na,not-checked}booleanfast | live | fixfast | network | live | fixinfo | warning | errorinfo | warning | criticalDecision:
main's 3-axis model is canonical; PR #71's boolean triple is dropped, not merged.A
boolean verifiedcannot express not-checked. Fast mode performs no network I/O by contract, so every remote axis is unchecked — under PR #71's shape that must serialize asverified: false, which is indistinguishable from checked and failed.main'snot-checkedstate keeps them distinct, andmain's version is also the newer one. Shipping both would put two mutually incompatible payloads behind oneschema_version: 2.Two further decisions, recorded in full in the artifact:
--deepstays read-only. PR feat(skills): Skills & Health — skill runtime, instruction resolver, doctor v2, support bundle #71 aliases it to--network, which silently adds network I/O to a flag that performs none today.--networkwill land as a new, explicitly named mode instead.main's engine content is kept. PR feat(skills): Skills & Health — skill runtime, instruction resolver, doctor v2, support bundle #71'sdev_session_live.tsis 154 lines againstmain'sdoctor_live.tsat 991; a naivetheirsresolution would have replaced the live engine with a 6x thinner one, and re-landed 9 checksmainalready has.Full conflict matrix, per-surface decision record, and findings table:
_loopstate/LOOP-01/sc-a0-2026-08-19/AUDIT-ARTIFACT.mdImplementation
This PR is A0.1 — the foundation only. It lands the part of PR #71 that
mainhas no equivalent of. Notably, it applied tomainunmodified: the port required zero source edits, which is itself evidence that the conflict was confined to the doctor engine.Nothing is wired into the CLI yet, so this PR changes no user-visible behaviour. It is additive and inert until A0.3.
Tests
Run against this exact tree at
d1b75ab:npm ci --ignore-scriptsnpm run typechecknpm testBaseline re-measured on clean
41a7e261in the same session: 922 pass / 0 fail. The 84 new tests are PR #71's skill and instruction suites, ported unchanged: trust/modify/re-trust lifecycle, lock drift, duplicate skill IDs across scopes, dependency cycles, lazy-loading token bounds, nestedAGENTS.mdprecedence and provenance, conflicting-instruction detection.No result here is inherited from PR #71's branch. PR #71 has no CI at all —
gh pr view 71 --json statusCheckRollupreturns an empty rollup — so nothing on it could have been inherited even if we wanted to.Security notes
test/skill_trust_lock.test.ts.test/skill_policy_packet.test.ts.Known limits
aether skills,/skills,aether capabilities, and the packaged built-in skill assets all land in A0.3.mainhas no equivalent of, and they need porting from PR feat(skills): Skills & Health — skill runtime, instruction resolver, doctor v2, support bundle #71'sCheckSpecontomain'sCheckOutcome. That is A0.2.--schema v2JSON selection and the JUnit renderer from PR feat(skills): Skills & Health — skill runtime, instruction resolver, doctor v2, support bundle #71 are written against the droppedDoctorReportV2. They are deferred: either re-targeted atHealthReportin A0.3, or dropped.package.jsonrepositoryandbugsstill point atDBarr3/aether-agent, notAetherAI3/aether-agent. Left for the integration lane rather than fixed opportunistically here.Dependency and merge order
SC-A1(tool runtime) andSC-A5(usage continuity) both start from the accepted A0 head — PR #71 also modifiessrc/core/stream.ts, which both of those lanes own.PR #71 should stay open until A0.3 merges, then be closed as superseded rather than merged. Its doctor engine is deliberately not carried forward.
Update — scope now covers A0.1 and the first half of A0.2
This branch has grown past the "foundation only" description above. Recording the
change rather than leaving a stale body.
Commits on this PR
d1b75ab1521199ba7d041Why one PR instead of the planned three. A0.1 is inert on its own and A0.2's
support bundle depends on it directly, so splitting them would produce a first PR
no reviewer can exercise and a second that cannot be reviewed without the first.
At ~37 files this is still well under PR #71's 100, and the module boundaries make
it readable. A0.3 (CLI wiring, packaged skill assets, docs) stays a separate PR.
CodeQL finding, fixed
CI flagged
js/incomplete-sanitization(high) atinstruction_resolver.ts:18—the glob metacharacter escape used a non-global regex, so only the first
metacharacter was escaped.
Not exploitable as written:
charisglob[index], always a single code unit,so a second occurrence cannot exist. But the escape's safety rested on an invariant
the function never stated, and a future change to a multi-character token would
silently widen every glob's match set. Fixed rather than dismissed.
The regression test pins the property, not the implementation: a glob containing
. + ( ) | { } [ ] $ ^must match itself literally and must not match a decoy,while
**and*keep working. Mutation-checked — replacing the escape with abare
pattern += charfails the test (unescaped . would match axts); restoringit passes.
This defect arrived from PR #71 untouched. PR #71 runs no CI, so on its own branch
it would have shipped unseen.
A0.2 part 1 — the one real port
capabilities.ts,redaction.ts,tar.tsand the generated capability snapshotapplied unmodified.
support_bundle.tsneeded two lines: it called PR #71'sdoctorReportV2, which this rescue drops in favour of main'sdiagnosticReport.The swap strictly improves the bundle. Under PR #71's boolean model a fast-mode
bundle — which performs no network I/O — had to serialize
verified: falseforevery remote axis, indistinguishable from checked and failed. main's
HealthReportcarries per-axisnot-checked, so an unexercised probe staysvisibly unexercised in the artifact a user hands to support.
Gates at
ba7d041npm run typechecknpm testnpm run smokeTest progression, each measured on the exact tree: 922 (clean
41a7e261) → 1006(A0.1) → 1007 (CodeQL regression test) → 1017 (A0.2 part 1).
The smoke failure is operator-owned, not a regression.
cloud turnreturnsHTTP 401 "Invalid or expired session token". Verified by re-running smoke at
1521199with this commit's changes stashed — identical failure. It needsaether auth login. No file in this PR is on the cloud-turn path. Reported as afail, not rounded up to a pass.
Bundle safety suites pass: a seeded canary secret is rejected rather than shipped,
and an interrupted generation leaves no misleading "complete" artifact.
Still owed before this PR leaves draft
skillsandinstructionsdoctor checks (theonly two it has that main lacks) from its
CheckSpeconto main'sCheckOutcome,plus the safe-repair reconciliation.
A0.3 (CLI wiring, built-in skill assets,
aether skills,/skills,aether capabilities, docs) remains a separate follow-up PR. Until it lands, noneof this is reachable from the command line — it is additive and inert.