From 785e5d844db6d1ab8a96cbe51ef47d870d9d9bc1 Mon Sep 17 00:00:00 2001
From: pate0304
Date: Fri, 31 Jul 2026 23:40:29 -0400
Subject: [PATCH 1/6] Add voice-friendly plan approval
---
CHANGELOG.md | 7 +
PROJECT_GOAL.md | 3 +-
README.md | 5 +-
artifacts/CHECKSUMS.sha256 | 33 ++-
docs/ARCHITECTURE.md | 7 +-
docs/CONFIG_SAFETY.md | 21 +-
docs/DEFINITION_OF_DONE.md | 5 +
docs/PRIVACY.md | 6 +
docs/THREAT_MODEL.md | 14 +
docs/USAGE.md | 35 ++-
docs/VALIDATION.md | 7 +-
docs/VOICE_APPROVAL.md | 103 +++++++
plugins/codsemble/scripts/codsemble.mjs | 258 ++++++++++++++++-
.../codsemble/skills/initialize-team/SKILL.md | 34 ++-
plugins/codsemble/skills/update-team/SKILL.md | 25 +-
src/cli.ts | 54 +++-
src/confirmation.ts | 274 ++++++++++++++++++
tests/cli.test.ts | 194 ++++++++++++-
tests/confirmation.test.ts | 148 ++++++++++
19 files changed, 1153 insertions(+), 80 deletions(-)
create mode 100644 docs/VOICE_APPROVAL.md
create mode 100644 src/confirmation.ts
create mode 100644 tests/confirmation.test.ts
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8733584..6e990ae 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,13 @@ until a release is explicitly published.
## Unreleased
+### Added
+
+- Voice-friendly plan approval with a strict six-word spoken challenge derived
+ from the unchanged full confirmation digest.
+- A read-only `approval` command that clearly separates terminal preview plans
+ from apply-capable plans.
+
## 0.1.0 - 2026-07-31
### Added
diff --git a/PROJECT_GOAL.md b/PROJECT_GOAL.md
index d869837..378e352 100644
--- a/PROJECT_GOAL.md
+++ b/PROJECT_GOAL.md
@@ -8,7 +8,8 @@ Build a clean Apache-2.0, offline-first, open-source Codex plugin that:
4. Lets the user search, customize, add, or exclude specialist roles.
5. Generates native `.codex/agents/*.toml`, a bounded managed `AGENTS.md` section,
and an optional version-aware project concurrency setting.
-6. Shows exact diffs and applies only the confirmed transaction.
+6. Shows exact diffs and applies only the confirmed transaction, using either
+ the full content-bound confirmation ID or its strictly matched voice-friendly alias.
7. Preserves unrelated content, detects concurrent modification, and supports safe rollback.
8. Validates models, permissions, tools, configuration, discovery, and real native delegation.
9. Produces a reproducible `v0.1.0` release candidate with complete open-source documentation.
diff --git a/README.md b/README.md
index 97dd9c4..f3b2179 100644
--- a/README.md
+++ b/README.md
@@ -11,6 +11,7 @@
Usage ·
How it works ·
Safety ·
+ Voice approval ·
111-role catalog
@@ -83,7 +84,8 @@ $initialize-team Set up a balanced Codex team for this workspace.
```
Codesemble audits and prepares a side-effect-free plan first. It applies project
-files only after showing the exact diff and receiving the plan's confirmation ID.
+files only after showing the exact diff and receiving the plan's confirmation ID
+or its strictly matched, voice-friendly spoken alias.
### Keep it healthy
@@ -121,6 +123,7 @@ credentials, broaden permissions, or publish anything for you.
| Install, initialize, update, diagnose, or roll back | [Usage](docs/USAGE.md) |
| Understand the compiler and native Codex outputs | [Architecture](docs/ARCHITECTURE.md) |
| Review concurrency, no-clobber apply, and recovery behavior | [Configuration safety](docs/CONFIG_SAFETY.md) |
+| Approve an apply-capable plan from a realtime voice session | [Voice-friendly approval](docs/VOICE_APPROVAL.md) |
| Browse the 111 specialist blueprints | [Role catalog](docs/ROLE_CATALOG.md) |
| Understand local data handling | [Privacy](docs/PRIVACY.md) |
| Review trust boundaries and abuse cases | [Threat model](docs/THREAT_MODEL.md) |
diff --git a/artifacts/CHECKSUMS.sha256 b/artifacts/CHECKSUMS.sha256
index 0e65a32..6644f97 100644
--- a/artifacts/CHECKSUMS.sha256
+++ b/artifacts/CHECKSUMS.sha256
@@ -17,18 +17,19 @@ cc6c4a85c49b34d8d34881be8b84975668eda056049b31ae5c5c1626fabed6a7 AGENTS.md
4fc4a92325b0ef2ca5b58c3aa968d0ab462698f001249d4d3974cb838576737a assets/brand/codsemble-github-hero.png
e9fa0ce1862c3da704746c8ccd0d6df504c8eae65a8a61992507b57f2dda2d5e assets/brand/codsemble-github-social-preview.png
ca34cfa4c7ad03edfc6b1dcf764fe44f6cb3465480f7177392ecf2c22e3c979c assets/social/concepts/codsemble-concept-native-compiler.png
-5d6bc5e8f821ca658c88c7d68eac23afcd01d64ccba2260e297ff2eef8564335 CHANGELOG.md
+3997a1587097662d82cdbf2310449585daec448d91fcba3f41339a0456fb1525 CHANGELOG.md
8237734d45581f5ae60b5c0ba597b729846e8a5dcd3d3d50a6535fd0d09f6a5f CODE_OF_CONDUCT.md
e5f38de7b71097b9017788638f7972d9e45e6ea98022131290f4742eaca0e66d CONTRIBUTING.md
-7a313396aba041ef310d09c9c3c396734a7df3e7a1263ae7bfa5f006bb22094b docs/ARCHITECTURE.md
+eb5bf40dd1178dd817898802d2b9207e55f51200e61cecdc630d9e3df0826b74 docs/ARCHITECTURE.md
44b00b27a3d9a28c65a6fe943e998e892ff14818a3624bad27ad93433e3aa84d docs/BRAND.md
-1a7888a1a2f86a3854cb4cebd8dd306510f856db1d96ad6fff77dea1a044d328 docs/CONFIG_SAFETY.md
-006a57921249687358bf65f8c66bf290a98b8a622745a6dc03ae08100e951915 docs/DEFINITION_OF_DONE.md
-b9a77ceb0f600f817e1bec1b4c6d41e893e243769da4cbe393d6a00de2490772 docs/PRIVACY.md
+268f623443157ecd9cf0236f358168a67c63001b48944d76bdb3310dc1d6bc4c docs/CONFIG_SAFETY.md
+7808ddd0377e597a4e30307e88eb19e36f77d1cbb00064c25725149baebc9a7a docs/DEFINITION_OF_DONE.md
+554d5be8524983d3715e3f8fb01382c96d21a31c8532a7aa17dfa41664be250d docs/PRIVACY.md
063e8aebc6f10ebb49b7cb6cc4892590eb309e727e20da4fc2bc88c38800f183 docs/ROLE_CATALOG.md
-68903e023e85ad5e6d6642102f5cace3392ae539be7455d1747a2d44b2324038 docs/THREAT_MODEL.md
-5da3eccc4e3deb8e5abe23430359844f8eccadd6e5611c5c6e8491ef21eb99d5 docs/USAGE.md
-7347bc68d26d0758b4d8440e0aac01d972e93fbaf6ec17e64575f12da58b2d32 docs/VALIDATION.md
+114cee8bbc4f32e61c4576119a32185a8681606ffda3cf2021597f2bdde94898 docs/THREAT_MODEL.md
+44676cd4783557174765c28ea584ab42b4f3b45f41ca34ac82742e520a7be23b docs/USAGE.md
+d46734315af1d2fcdd34cae0ccbc7a1927080ed4016ce157aebc0d69e34a6e63 docs/VALIDATION.md
+2a48ed92f5e11d3b31519cb24e17644f9367015bfb51abad29720a46fb0b5c53 docs/VOICE_APPROVAL.md
41e996c5a736d7d7011bb1eba7cdfaff1ec0379c98bbc9351333824962e63fcd examples/intake.preview.json
06b6a2f5136c646ae2da2cd1125ad656dc176e36177ff032f95778b5d3558889 examples/intake.runtime.json
de1ecec80ccbbcaa886612d89bd7584e4d9640e9d0d4ec0071ed9f665a212f48 GOVERNANCE.md
@@ -39,17 +40,17 @@ b89d0cb888d7afade19b1694a4a2c2ba44a45038cf3a02565631b54463c1b556 NOTICE
5ab43170313732edb527ff5502ce62143e0644087b894f3daf16e9abc715c17d plugins/codsemble/.codex-plugin/plugin.json
a668f71b534f87bdf585b35dfdd3760f06b7bbe08498fe3af335805b2fa2c61b plugins/codsemble/catalog/generate.mjs
edd6d03447adb073a2aebd71e0e0b94b5c3a6fa31f4492cd6035623076b672b6 plugins/codsemble/catalog/roles.json
-6585bd1e11301a27fb6da65eb7b6f769fe15f083f22649849ca8f82aa03f74db plugins/codsemble/scripts/codsemble.mjs
+9fa248ea621bf494c82868a56b7a2d9f57a0f9a99e45a62b613615e45f85f24f plugins/codsemble/scripts/codsemble.mjs
887c3b5fa744a0214988b5d8422c22f385a626bc19ba4582913b2d25ff621fb3 plugins/codsemble/skills/initialize-team/agents/openai.yaml
-a0a3aab3c168d627362c2d753ba540f2e4217b0b88636cbd1ec1ff95c279892f plugins/codsemble/skills/initialize-team/SKILL.md
+178408317908dae2754526dae79579a7fe33ef7e4e0c8f71ee3b649a158bbdbe plugins/codsemble/skills/initialize-team/SKILL.md
1a476efd980ab66f88580650102255b45b26b8a7c7ea74471a8c985915fd8f8d plugins/codsemble/skills/rollback-team/agents/openai.yaml
96fdc5269e2e2c8f55d3a0c5f4a01421e806b51ef205953d795eeac545e89cdb plugins/codsemble/skills/rollback-team/SKILL.md
ec89a95f29bb67625a44663c08b0939cf522158cd094e31015b83c1a20c2062b plugins/codsemble/skills/team-doctor/agents/openai.yaml
721e7ae8fae15f63dfd9e890fe8b1431c178ad056a7a1f189d76cd7c30b1d420 plugins/codsemble/skills/team-doctor/SKILL.md
a6689d5a15007ba0942ca9ff9538be7a3fc8e1c4b0993191b928063161f57098 plugins/codsemble/skills/update-team/agents/openai.yaml
-8bd685b860157550257bda3741052848a0b681fe1bfa151870a89a8c06c2f9e2 plugins/codsemble/skills/update-team/SKILL.md
-1df3758ba8adb15272c57801349177a25cafefadfdad1e657a4ec050be33f82b PROJECT_GOAL.md
-0dd1249861887986eae2bab002ae3b223bdec1c20df29745b558a47c944563f0 README.md
+f29be74151b0344f63168d304802f7e4f7a0e498dd2960feee81a04dbcc979a1 plugins/codsemble/skills/update-team/SKILL.md
+a97900e92d8102cde373167dc46d1cfb6d62e697b117322be5972448b94d78aa PROJECT_GOAL.md
+9b3fea3f21a878e8dcd6eaf32fbc71bd1d8a2d602bfd835dd86489cf1aeb7c73 README.md
f1dbedae34f6dcadc5bd10764ceeec842a03a75d33a984af8e5569f9136da291 ROADMAP.md
d2d0dd1a73d5d788475bc040d23b617f7d0ee324d8b40bd081a68b27eada18d2 scripts/build.mjs
c6581520fed9f775f87ef1e054c46e786129f03d79b7ad31a441c59e4bdd343a scripts/checksums.mjs
@@ -60,9 +61,10 @@ ce3cc1e66b2c89a5103aa3360ef04a60b0c983f84b7221ef3174cb8778f9795d SECURITY.md
03acaff079500461df8d493d1614ac530fc5fc67e8c15b80a6beb174061e7c62 src/audit.ts
4330112db7e3a02439292a6fae4cf94d95d52369fe54e07107391274135034c9 src/capabilities.ts
9961f538a6de4dfd06a6575bf1c626924bce62f86ac0573f4eb288aa8e8652b1 src/catalog.ts
-66f410ca462298e40b278cbe98abc0ab813617170205b78777befa9e860cbeb4 src/cli.ts
+f65166f09c75deec11f0ab70aa92a751ac043e202fcf450a88965fa72505252f src/cli.ts
0f3d13e593cb39947a71ba9f4ef5116b59e27a626200a9a3f185095d8b908d24 src/compiler.ts
3327d991ca0a072abf459e981776ae7f12fb3b66299f20c21b12f1453d9daace src/config.ts
+1edeef8a01805a166f1198971d966c99ef51d232e6667e7b9e8e8a69bf748721 src/confirmation.ts
f858f1cc207292cd0230171a6bdc3420271009c2377e88343eb03dd0a9ebdc5e src/doctor.ts
c1cf10fc73088456627a01ad7e23e6760910be3014e9189db23483e0d594fd20 src/recommend.ts
9498a7f07b699530ddf40714231801e6f3b05ee91a7feb31a7e388ade5585400 src/schemas.ts
@@ -73,9 +75,10 @@ c1cf10fc73088456627a01ad7e23e6760910be3014e9189db23483e0d594fd20 src/recommend.
9f8a49ebcf6c519d3aa6aab3b8354381fa46b44a17647035583d9d1dd15ea507 tests/audit.test.ts
34fdf298b89d17f94be38f6a43f00c7d92d1e0a7f4617f95134d7c2834b0ef02 tests/capabilities.test.ts
eb08ca80b5984c3a69e7506548e2680c405386b3f320c8cc0a1e69b59ec8f3b3 tests/catalog.test.ts
-70f3712baecc957f042ec992178e3d03718263b409914fac04218c4d46431fd9 tests/cli.test.ts
+1ced964e4e034f0317f2b3b6503aa5ca990dd54cfc5fb0f9951040a2e8384ffb tests/cli.test.ts
f9e6d118c9cd4b52b060b0bcf6aa0fc53cce1b0c9398e51667600e393defbdbf tests/compiler.test.ts
69d4b23d9744ab8c46c54d184a705a2969b3f293151557b0507cfc467250348b tests/config.test.ts
+4ba49ef01a94551fdef79788faa3b6f3bd9f03178d72eabcece788bfa102b517 tests/confirmation.test.ts
6a5a6bfc3fc981a335107bb929388d1fc392b661eaf1d5a7eead9796776a2a85 tests/doctor.test.ts
c34cbe8eef0a1024cdebaf2df04a7d42da0f6e75a05a77e3fffaa028d5b61697 tests/fixtures/audit/typescript-app/.codex/agents/reviewer.toml
609b0270c380969286ce2be72aad387758e8169c221f93c3bb14a4b352c5e65c tests/fixtures/audit/typescript-app/.codex/codsemble/manifest.json
diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md
index 50b33b5..27b129a 100644
--- a/docs/ARCHITECTURE.md
+++ b/docs/ARCHITECTURE.md
@@ -13,7 +13,8 @@ workspace
-> deterministic role ranking
-> Lean / Balanced / Full proposals
-> exact generated-file and configuration preview
- -> confirmed, locked no-clobber transaction
+ -> full-digest or strictly matched voice-alias confirmation
+ -> locked no-clobber transaction
-> doctor and fresh-session validation
```
@@ -43,6 +44,10 @@ at runtime.
- Recommendation consumes typed signals, not arbitrary repository prose.
- Preview has no side effects.
- Apply requires a content-bound confirmation id and unchanged preimage hashes.
+- Voice approval is a derived presentation alias for that unchanged digest, not
+ a shorter replacement, secret, or speaker-authentication mechanism.
+- Preview-only plans never expose a voice challenge and must be regenerated in
+ an apply-capable mode before confirmation.
- Plan binds answer-file claims to a live local capability probe; apply
independently re-probes the requirements encoded in the confirmed plan.
- Mutations use a cooperative lock, durable pending record, quarantine, and
diff --git a/docs/CONFIG_SAFETY.md b/docs/CONFIG_SAFETY.md
index 58eb8c4..b0ac312 100644
--- a/docs/CONFIG_SAFETY.md
+++ b/docs/CONFIG_SAFETY.md
@@ -42,14 +42,17 @@ run applicable Codex diagnostics after apply.
## Preview and apply
-`audit`, `recommend`, `plan`, `doctor`, `catalog`, and rollback preview are
-non-mutating. `plan` emits a content-bound confirmation id, exact intended files, content hashes, and
-configuration changes.
+`audit`, `recommend`, `plan`, `approval`, `doctor`, `catalog`, and rollback
+preview are non-mutating. `plan` emits a content-bound confirmation id, exact
+intended files, content hashes, and configuration changes. `approval` reports
+whether that plan can be applied and derives a voice-friendly alias only for a
+non-preview plan.
Apply requires:
- the reviewed plan file;
-- exact confirmation of that confirmation id;
+- exact confirmation of that confirmation id, or an exact strict match of its
+ complete current voice challenge;
- unchanged preimage hashes;
- paths confined to the selected workspace;
- valid generated TOML and JSON.
@@ -61,6 +64,13 @@ cannot be silently deleted. A file recreated before publication causes a
no-clobber conflict; both the competing target and quarantined bytes are
retained. Rollback applies the same checks to confirmed postimages.
+The voice challenge keeps the full confirmation digest as the canonical plan
+binding. It accepts no fuzzy or semantic matching and is not an authentication
+secret. Freshness remains state-based: changed preimages invalidate the plan,
+and a successful mutating apply makes an immediate replay fail. See
+[Voice-friendly plan approval](VOICE_APPROVAL.md) for the precise contract and
+its explicitly excluded trusted-broker guarantees.
+
For every successful update or delete, the transaction receipt records and
retains the source quarantine. Codesemble does not automatically unlink it:
an editor may still hold the original inode open and write after pathname
@@ -82,7 +92,8 @@ crash recovery is deferred beyond v0.1.
## Manual mode
Choose `manual` or `unchanged` during intake when project config should not be
-edited. After exact confirmation-id approval, Codesemble may still apply the team
+edited. After exact confirmation-id or strict voice-challenge approval,
+Codesemble may still apply the team
agents, managed `AGENTS.md` section, and manifest while leaving
`.codex/config.toml` untouched. `manual` also shows the exact project snippet
for separate installation; `unchanged` preserves concurrency as-is. Global
diff --git a/docs/DEFINITION_OF_DONE.md b/docs/DEFINITION_OF_DONE.md
index a7b29da..b7c0844 100644
--- a/docs/DEFINITION_OF_DONE.md
+++ b/docs/DEFINITION_OF_DONE.md
@@ -20,6 +20,9 @@ Codesemble `v0.1.0` is a release candidate only when all applicable items pass.
without clobbering a racing writer, and is reversible during an uninterrupted
cooperative transaction.
- The exact confirmation id is recomputed from the complete plan before apply.
+- Preview-only plans have no approval challenge. Apply-capable plans may expose
+ a strictly matched voice-friendly alias while retaining the full digest as
+ the canonical binding; vague, partial, fuzzy, and cross-plan speech fails.
- Update deletes only stale agent files owned by the prior Codesemble manifest.
- No global config, trust, credentials, plugins, MCP servers, hooks, or external systems are changed.
@@ -27,6 +30,8 @@ Codesemble `v0.1.0` is a release candidate only when all applicable items pass.
- Type checks, unit tests, fixture tests, golden tests, property tests, and security tests pass.
- Plugin and every skill pass the official validators.
+- Voice-confirmation tests prove exact plan binding, strict normalization,
+ preview refusal, compatibility with the full ID, and no writes on rejection.
- The release payload is reproducible and contains no secrets or absolute developer paths.
- A fresh isolated Codex session discovers the plugin and generated roles.
- A real separable task produces attributable specialist results and root integration.
diff --git a/docs/PRIVACY.md b/docs/PRIVACY.md
index 17eb702..582285d 100644
--- a/docs/PRIVACY.md
+++ b/docs/PRIVACY.md
@@ -13,6 +13,12 @@ Codesemble may persist the selected team, generator versions, content hashes,
and transaction metadata under `.codex/codsemble/`. Transaction records do not
need to store arbitrary source content or secret values.
+The voice-friendly path processes only the transcript string passed to the
+local CLI. Codesemble does not record audio, identify a speaker, or persist the
+spoken challenge or transcript in the workspace or transaction receipt. The
+calling voice surface may have separate data handling that remains outside this
+plugin's boundary.
+
## Data excluded
The auditor excludes:
diff --git a/docs/THREAT_MODEL.md b/docs/THREAT_MODEL.md
index 37b0dab..261a40b 100644
--- a/docs/THREAT_MODEL.md
+++ b/docs/THREAT_MODEL.md
@@ -20,6 +20,7 @@
8. Inventing unavailable model ids or claiming a configuration is active without runtime proof.
9. Installing or executing unreviewed third-party code.
10. Confusing a local release candidate with a public or directory-published plugin.
+11. Treating vague, approximate, replayed, or cross-plan speech as installation approval.
## Required controls
@@ -35,6 +36,19 @@
- Default read-heavy roles to read-only and reject dangerous generated settings.
- Bound fan-out, depth, retries, time, and generated file counts.
- Keep telemetry and network access off in v0.1.0.
+- Keep the full plan digest authoritative for voice approval; derive only a
+ versioned spoken alias, require an exact conservative transcript match, give
+ preview plans no challenge, and recheck capabilities and preimages before writes.
+
+## Voice limitation
+
+The CLI can prove that a transcript exactly matches the alias of the plan being
+applied. It cannot prove who spoke, distinguish live speech from playback, bind
+the phrase to a particular Android device or session, or hide the phrase from a
+same-user process. A trusted voice broker with protected input, expiring signed
+grants, rate limits, and an atomic consumption ledger is required for those
+stronger guarantees. Codesemble therefore treats the phrase as explicit-intent
+UX, not authentication, and retains the full digest and filesystem controls.
## Filesystem limitation
diff --git a/docs/USAGE.md b/docs/USAGE.md
index 2cb806b..061e0e6 100644
--- a/docs/USAGE.md
+++ b/docs/USAGE.md
@@ -59,8 +59,20 @@ These commands emit JSON to standard output and do not write workspace files.
multi-agent feature state, and bounded model metadata. It discards raw provider
instructions and cannot grant permissions. If probing fails, keep model
configuration inherited and use manual or unchanged config mode.
-Save the plan outside the workspace, inspect every proposed path and diff, then
-apply with the exact confirmation id, which is a digest of the complete plan:
+Save the plan outside the workspace and inspect every proposed path and diff.
+Then ask the read-only approval command whether the plan is apply-capable:
+
+```bash
+node "/scripts/codsemble.mjs" approval \
+ --plan "/temporary/path/plan.json"
+```
+
+A `preview-only` result is terminal: it has no approval challenge and cannot be
+passed to `apply`. To make changes, re-probe and regenerate a new plan using
+`apply-project`, `manual`, or `unchanged` mode, then show its exact diff.
+
+For an apply-capable plan, use either the exact confirmation id, which is a
+digest of the complete plan:
```bash
node "/scripts/codsemble.mjs" apply \
@@ -69,9 +81,24 @@ node "/scripts/codsemble.mjs" apply \
--confirm ""
```
+or its complete voice challenge:
+
+```bash
+node "/scripts/codsemble.mjs" apply \
+ --workspace "/absolute/path/to/workspace" \
+ --plan "/temporary/path/plan.json" \
+ --confirm-voice "approve team "
+```
+
+Voice matching accepts only case, whitespace or hyphen separators, and one
+terminal punctuation mark. `yes`, `continue`, `go ahead`, partial phrases,
+reordered words, approximate matches, and cross-plan challenges are refused.
+See [Voice-friendly plan approval](VOICE_APPROVAL.md).
+
`apply` is the mutating boundary. Do not infer confirmation from an earlier
-general request; show the final exact diff and ask for confirmation of the plan
-id. In `manual` and `unchanged` modes, apply writes only the confirmed team
+general request, proposal choice, positive feedback, or read-only consent; show
+the final exact diff and ask for the current confirmation ID or complete voice
+challenge. In `manual` and `unchanged` modes, apply writes only the confirmed team
artifacts and leaves `.codex/config.toml` untouched. `preview` performs no
writes. An already-identical plan returns `noChanges: true` with no transaction
receipt and no reload request.
diff --git a/docs/VALIDATION.md b/docs/VALIDATION.md
index e95fc3d..d2b7e80 100644
--- a/docs/VALIDATION.md
+++ b/docs/VALIDATION.md
@@ -20,8 +20,8 @@ npm audit --audit-level=high
The current candidate passes:
- strict TypeScript checking;
-- 87 unit, golden, property, security, fixture, transaction, capability,
- doctor, compiler, and bundled-CLI tests;
+- 94 unit, golden, property, security, fixture, transaction, capability,
+ voice-confirmation, doctor, compiler, and bundled-CLI tests;
- deterministic bundle generation;
- full source-payload checksum verification;
- exactly 111 schema-valid, uniquely identified role blueprints;
@@ -89,4 +89,7 @@ they referenced local authentication state.
- CodeQL passed on the same source. See
[CodeQL run 30624509442](https://github.com/VAMFI/codsemble/actions/runs/30624509442).
- Real-runtime proof currently covers one macOS arm64 host and Codex 0.145.0.
+- Voice tests use transcript strings. They do not prove Android microphone,
+ realtime echo suppression, speech recognition, speaker identity, or trusted
+ UI channel binding.
- OpenAI plugin-directory submission is a separate external review boundary.
diff --git a/docs/VOICE_APPROVAL.md b/docs/VOICE_APPROVAL.md
new file mode 100644
index 0000000..90a72a8
--- /dev/null
+++ b/docs/VOICE_APPROVAL.md
@@ -0,0 +1,103 @@
+# Voice-friendly plan approval
+
+## Goal
+
+Make Codesemble's human approval boundary practical in realtime voice sessions
+without replacing the full content-bound confirmation digest or accepting vague
+speech as authorization.
+
+## Interaction contract
+
+A plan has one of two approval states:
+
+- `preview-only`: the plan is terminal and has no voice challenge. It cannot be
+ passed to `apply`. If the user later wants changes, Codesemble re-probes the
+ runtime and workspace and creates a new plan in `apply-project`, `manual`, or
+ `unchanged` mode.
+- `ready`: the read-only `approval` command derives a spoken alias from the
+ plan's full 128-bit confirmation digest. The phrase has the form
+ `approve team `.
+
+The spoken phrase is an explicit-intent and plan-mix-up barrier. It is not a
+password, speaker-authentication factor, or substitute for the full digest.
+Codesemble recomputes that digest before accepting either confirmation method.
+
+```bash
+node "/scripts/codsemble.mjs" approval \
+ --plan "/temporary/path/plan.json"
+
+node "/scripts/codsemble.mjs" apply \
+ --workspace "/absolute/path/to/workspace" \
+ --plan "/temporary/path/plan.json" \
+ --confirm-voice "approve team violet cotton lemon summit spiral cactus"
+```
+
+The exact `--confirm ` path remains available for keyboard and
+automation compatibility. Exactly one confirmation method is permitted.
+
+## Literal matching
+
+Voice matching is mechanical rather than model-judged. It accepts only:
+
+- ASCII English words;
+- case differences;
+- collapsed whitespace or hyphens between words; and
+- at most one terminal `.`, `!`, or `?` added by transcription.
+
+It rejects extra, missing, repeated, or reordered words; internal punctuation;
+Unicode confusables; approximate matches; synonyms; homophones; and phrases
+such as `yes`, `continue`, `go ahead`, `approved`, or `do it`.
+
+In a voice session, the assistant must finish presenting the exact plan before
+asking for the challenge. The calling voice layer must arm confirmation only
+after assistant speech ends and pass a later user-originated transcript. If it
+cannot distinguish that turn from assistant echo or playback, it must not use
+`--confirm-voice`; use the keyboard confirmation-ID path instead. A failed match
+leaves the workspace unchanged and must receive exactly this recovery response:
+
+> That did not match. Nothing changed. Repeat the exact phrase shown, or say cancel.
+
+`cancel` disarms the conversational approval step and must not invoke `apply`.
+
+## Binding and freshness
+
+The voice phrase is deterministically derived from the complete confirmation
+digest with a versioned domain separator. Any change to roles, models,
+permissions, concurrency, generated bytes, actions, or preimage hashes changes
+the digest and therefore changes the phrase.
+
+Freshness is state-based, not time-based:
+
+- apply revalidates the full digest and live Codex capabilities;
+- every current workspace preimage must still match the reviewed plan;
+- a successful mutating apply changes those preimages, so immediate replay of
+ the same plan fails closed;
+- an already-identical plan performs verification only and creates no receipt;
+- rollback can restore the original preimages, at which point the same plan is
+ technically eligible again, but the workflow requires a new explicit human
+ confirmation before another apply attempt.
+
+The CLI does not claim that the phrase is secret, expiring, session-bound,
+speaker-bound, or durably single-use. Those guarantees require a trusted voice
+broker and an atomic signed-grant ledger outside the current offline plugin
+boundary.
+
+## Definition of Done
+
+- Preview-only plans expose no voice challenge and fail before confirmation is
+ requested.
+- Apply-capable plans expose a six-word pronounceable challenge without changing
+ deterministic plan contents.
+- The legacy exact confirmation ID remains byte-exact and supported.
+- Voice confirmation retains full-plan digest recomputation, capability checks,
+ preimage checks, transaction locking, no-clobber publication, and rollback.
+- Generic, partial, cross-plan, ambiguous, or Unicode-confusable transcripts
+ fail before capability probing or workspace mutation.
+- Skills explicitly separate review-only and apply-capable planning and never
+ infer approval from the initial request or positive conversational feedback.
+- Focused confirmation and CLI tests, the complete repository suite, official
+ plugin/skill validation, deterministic build, SBOM, checksums, and dependency
+ audit pass.
+- Microphone recognition, Android UI channel binding, echo suppression, speaker
+ identity, and trusted one-use grants remain separately reported runtime
+ boundaries rather than inferred from transcript tests.
diff --git a/plugins/codsemble/scripts/codsemble.mjs b/plugins/codsemble/scripts/codsemble.mjs
index aa624af..620895d 100755
--- a/plugins/codsemble/scripts/codsemble.mjs
+++ b/plugins/codsemble/scripts/codsemble.mjs
@@ -17759,6 +17759,221 @@ async function getExistingFile(root, relativePath, existingFiles) {
}
}
+// src/confirmation.ts
+var VOICE_CONFIRMATION_VERSION = "voice-v1";
+var VOICE_CONFIRMATION_WORDS = [
+ "acorn",
+ "admiral",
+ "almond",
+ "amber",
+ "anchor",
+ "anthem",
+ "apricot",
+ "arctic",
+ "atlas",
+ "badger",
+ "bamboo",
+ "banjo",
+ "beacon",
+ "beaver",
+ "biscuit",
+ "blossom",
+ "bonnet",
+ "bottle",
+ "bronze",
+ "cactus",
+ "candle",
+ "canyon",
+ "caramel",
+ "cedar",
+ "cello",
+ "cherry",
+ "cobalt",
+ "comet",
+ "copper",
+ "coral",
+ "cotton",
+ "crater",
+ "crystal",
+ "daisy",
+ "denim",
+ "desert",
+ "domino",
+ "dragon",
+ "driftwood",
+ "eagle",
+ "elmwood",
+ "emerald",
+ "falcon",
+ "feather",
+ "festival",
+ "flannel",
+ "forest",
+ "fossil",
+ "galaxy",
+ "garden",
+ "garnet",
+ "ginger",
+ "glacier",
+ "granite",
+ "harbor",
+ "hazel",
+ "helmet",
+ "honey",
+ "horizon",
+ "ivory",
+ "jacket",
+ "jasmine",
+ "kettle",
+ "kiwi",
+ "lantern",
+ "lavender",
+ "lemon",
+ "lilac",
+ "lobster",
+ "maple",
+ "marble",
+ "meadow",
+ "melon",
+ "meteor",
+ "mosaic",
+ "mountain",
+ "mustard",
+ "nectar",
+ "nickel",
+ "ocean",
+ "olive",
+ "orchid",
+ "otter",
+ "panda",
+ "paper",
+ "pebble",
+ "pepper",
+ "piano",
+ "pickle",
+ "planet",
+ "plum",
+ "pocket",
+ "quartz",
+ "rabbit",
+ "radar",
+ "raven",
+ "ribbon",
+ "river",
+ "rocket",
+ "saffron",
+ "sailor",
+ "satin",
+ "shadow",
+ "silver",
+ "socket",
+ "sparrow",
+ "spiral",
+ "spruce",
+ "summit",
+ "sunset",
+ "tablet",
+ "tango",
+ "teapot",
+ "temple",
+ "thunder",
+ "timber",
+ "topaz",
+ "tulip",
+ "velvet",
+ "violet",
+ "walnut",
+ "willow",
+ "window",
+ "winter",
+ "yogurt",
+ "yucca",
+ "zebra",
+ "zephyr"
+];
+var SPOKEN_WORD_COUNT = 6;
+function voiceChallengeForConfirmationId(confirmationId) {
+ if (!/^[a-f0-9]{32}$/.test(confirmationId)) {
+ throw new Error("Cannot derive a voice challenge from an invalid confirmation id");
+ }
+ const digest = sha256(
+ `${VOICE_CONFIRMATION_VERSION}\0${confirmationId}`
+ );
+ const pool = [...VOICE_CONFIRMATION_WORDS];
+ let value = BigInt(`0x${digest}`);
+ const selected = [];
+ for (let index = 0; index < SPOKEN_WORD_COUNT; index += 1) {
+ const selectedIndex = Number(value % BigInt(pool.length));
+ const word = pool.splice(selectedIndex, 1)[0];
+ if (word === void 0) {
+ throw new Error("Voice challenge vocabulary is incomplete");
+ }
+ selected.push(word);
+ value /= BigInt(pool.length + 1);
+ }
+ return `approve team ${selected.join(" ")}`;
+}
+function describePlanApproval(plan) {
+ assertConfirmationDigest(plan);
+ const applyCapable = plan.concurrency.configMode !== "preview";
+ const mutatingPaths = plan.files.filter(({ action }) => action !== "verify").map(({ relativePath }) => relativePath).sort();
+ return {
+ schemaVersion: 1,
+ planId: plan.planId,
+ confirmationId: plan.confirmationId,
+ state: applyCapable ? "ready" : "preview-only",
+ applyCapable,
+ noChanges: mutatingPaths.length === 0,
+ mutatingPaths,
+ voiceChallengeVersion: VOICE_CONFIRMATION_VERSION,
+ voiceChallenge: applyCapable ? voiceChallengeForConfirmationId(plan.confirmationId) : null,
+ freshness: {
+ mode: "plan-and-preimage-bound",
+ summary: applyCapable ? "Valid only for this exact plan while every recorded workspace preimage remains unchanged." : "Preview-only plans have no approval step and must be regenerated in an apply-capable mode."
+ }
+ };
+}
+function verifyPlanConfirmation(plan, confirmation) {
+ assertConfirmationDigest(plan);
+ if (plan.concurrency.configMode === "preview") {
+ throw new Error(
+ "Apply refused: preview plans are read-only; regenerate with apply-project, manual, or unchanged mode"
+ );
+ }
+ if (confirmation.kind === "full-id") {
+ if (confirmation.value !== plan.confirmationId) {
+ throw new Error(
+ "Confirmation refused: --confirm must exactly match plan.confirmationId"
+ );
+ }
+ return;
+ }
+ const expected = voiceChallengeForConfirmationId(plan.confirmationId);
+ const received = normalizeVoiceConfirmation(confirmation.value);
+ if (received === null || received !== expected) {
+ throw new Error(
+ "Voice confirmation refused: repeat the complete current voice challenge exactly; vague, partial, reordered, or approximate speech is not approval"
+ );
+ }
+}
+function normalizeVoiceConfirmation(value) {
+ if (value.length === 0 || value.length > 240 || !/^[\t\n\r\x20-\x7e]+$/.test(value)) {
+ return null;
+ }
+ const trimmed = value.trim().toLowerCase();
+ const withoutTerminalPunctuation = /[.!?]$/.test(trimmed) ? trimmed.slice(0, -1) : trimmed;
+ if (withoutTerminalPunctuation.startsWith("-") || withoutTerminalPunctuation.endsWith("-") || /[^a-z\s-]/.test(withoutTerminalPunctuation)) {
+ return null;
+ }
+ const normalized = withoutTerminalPunctuation.replace(/[\s-]+/g, " ").trim();
+ return /^approve team(?: [a-z]+){6}$/.test(normalized) ? normalized : null;
+}
+function assertConfirmationDigest(plan) {
+ if (!/^[a-f0-9]{32}$/.test(plan.confirmationId) || computeConfirmationId(plan) !== plan.confirmationId) {
+ throw new Error("Plan confirmation digest mismatch");
+ }
+}
+
// src/doctor.ts
import { access as access2, lstat as lstat5, readFile as readFile4, readdir as readdir3 } from "node:fs/promises";
import path6 from "node:path";
@@ -19687,13 +19902,15 @@ Usage:
codsemble capabilities [--workspace PATH]
codsemble recommend --answers FILE [--workspace PATH] [--catalog FILE]
codsemble plan --answers FILE --proposal lean|balanced|full [--workspace PATH]
- codsemble apply --plan FILE --confirm CONFIRMATION_ID [--workspace PATH]
+ codsemble approval --plan FILE
+ codsemble apply --plan FILE (--confirm CONFIRMATION_ID | --confirm-voice "VOICE_CHALLENGE") [--workspace PATH]
codsemble doctor [--workspace PATH]
codsemble rollback --transaction TRANSACTION_ID --confirm TRANSACTION_ID [--workspace PATH]
codsemble catalog [--search TERM] [--catalog FILE]
-Audit, capabilities, recommend, plan, catalog, and doctor are read-only. Apply requires the
-exact plan ID printed by plan. Project configuration is never changed globally.
+Audit, capabilities, recommend, plan, approval, catalog, and doctor are read-only. Apply accepts
+only a non-preview plan and either its exact confirmation ID or its complete current voice
+challenge. Generic approval words are never accepted. Project configuration is never changed globally.
`;
function parseArguments(argv) {
const [command, ...rest] = argv;
@@ -19809,25 +20026,44 @@ async function run(arguments_) {
assertPlanCapabilities(plan, capabilities, "plan");
return plan;
}
+ case "approval": {
+ allowOnly(arguments_, ["--plan"]);
+ const plan = await readJson(
+ flag(arguments_, "--plan", { required: true })
+ );
+ assertValidTeamPlan(plan);
+ return describePlanApproval(plan);
+ }
case "apply": {
- allowOnly(arguments_, ["--workspace", "--plan", "--confirm"]);
+ allowOnly(arguments_, [
+ "--workspace",
+ "--plan",
+ "--confirm",
+ "--confirm-voice"
+ ]);
const plan = await readJson(
flag(arguments_, "--plan", { required: true })
);
assertValidTeamPlan(plan);
- const confirmation = flag(arguments_, "--confirm", {
- required: true
- });
- if (typeof plan.planId !== "string" || confirmation !== plan.confirmationId) {
+ if (plan.concurrency?.configMode === "preview") {
throw new Error(
- "Confirmation refused: --confirm must exactly match plan.confirmationId"
+ "Apply refused: preview plans are read-only; regenerate with apply-project, manual, or unchanged mode"
);
}
- if (plan.concurrency?.configMode === "preview") {
+ const fullConfirmation = flag(arguments_, "--confirm");
+ const voiceConfirmation = flag(arguments_, "--confirm-voice");
+ if (fullConfirmation === void 0 === (voiceConfirmation === void 0)) {
throw new Error(
- "Apply refused: preview plans are read-only; regenerate with apply-project, manual, or unchanged mode"
+ "Apply requires exactly one confirmation method: --confirm or --confirm-voice"
);
}
+ verifyPlanConfirmation(
+ plan,
+ fullConfirmation !== void 0 ? { kind: "full-id", value: fullConfirmation } : {
+ kind: "voice-challenge",
+ value: voiceConfirmation
+ }
+ );
const capabilities = await detectCodexCapabilities(workspace);
assertPlanCapabilities(plan, capabilities, "apply");
if (plan.files.every(({ action }) => action === "verify")) {
diff --git a/plugins/codsemble/skills/initialize-team/SKILL.md b/plugins/codsemble/skills/initialize-team/SKILL.md
index dda4f2d..7e3efed 100644
--- a/plugins/codsemble/skills/initialize-team/SKILL.md
+++ b/plugins/codsemble/skills/initialize-team/SKILL.md
@@ -23,7 +23,8 @@ not download, install, or substitute another executable.
credentials, providers, hooks, MCP servers, or third-party skills/plugins.
- Never push, publish, deploy, release, submit, or message external systems.
- Do not infer apply approval from a request to initialize. Apply only after
- showing the final exact diff and receiving its exact confirmation id.
+ showing the final exact diff and receiving its exact confirmation id or the
+ complete current voice challenge.
- Keep installed role count separate from concurrent spawned workers. The
worker count excludes the primary/orchestrator thread. Never derive it from
the catalog size of 111.
@@ -96,22 +97,45 @@ not download, install, or substitute another executable.
exact diff. State that project config is a persistent default loaded only
when Codex trusts the project and may require a fresh session.
-7. For `preview`, stop after the plan. For `manual`, explain that apply will
+7. Run the read-only approval description:
+
+ ```text
+ node /scripts/codsemble.mjs approval \
+ --plan
+ ```
+
+ For `preview`, require `state: preview-only`, do not display or speak a
+ challenge, do not ask for confirmation, and stop after stating that the plan
+ is read-only and terminal. If the user later wants changes, re-probe and
+ regenerate a new non-preview plan; never promote the old preview. For
+ `manual`, explain that apply will
write the confirmed team artifacts while leaving `.codex/config.toml`
untouched, and show the concurrency snippet for the user to install
separately. For `unchanged`, explain that apply will write the confirmed
team artifacts without changing concurrency configuration.
-8. For any non-preview mode, ask the user to confirm the displayed exact plan
- confirmation id. Accept only an unambiguous match. Then run:
+8. For any non-preview mode, ask the user to confirm the displayed exact plan.
+ In voice interactions, speak the complete `voiceChallenge` only after the
+ diff and risk summary, then require a later user-originated turn to repeat it
+ exactly. Reject `yes`, `continue`, `go ahead`, `approved`, `do it`, partial
+ phrases, paraphrases, reordered words, and approximate matches. Do not ask a
+ yes/no repair question after a mismatch. Say exactly: `That did not match.
+ Nothing changed. Repeat the exact phrase shown, or say cancel.` If the user
+ says `cancel`, discard the conversational approval step and do not invoke
+ `apply`. Use voice confirmation only when the calling voice layer identifies
+ a later user-originated transcript after assistant speech ends; otherwise
+ require the keyboard confirmation-ID path. Then run:
```text
node /scripts/codsemble.mjs apply \
--workspace \
--plan \
- --confirm
+ --confirm-voice ""
```
+ For keyboard automation, `--confirm ` remains the
+ byte-exact compatibility path. Use exactly one confirmation method.
+
Do not alter the plan after confirmation. If any preimage changed, stop and
regenerate instead of retrying or overwriting.
diff --git a/plugins/codsemble/skills/update-team/SKILL.md b/plugins/codsemble/skills/update-team/SKILL.md
index 13618c2..3c19ca4 100644
--- a/plugins/codsemble/skills/update-team/SKILL.md
+++ b/plugins/codsemble/skills/update-team/SKILL.md
@@ -25,7 +25,8 @@ reconstruct ownership from guesses or download replacement tooling.
- Ask separately for installed role count and concurrent spawned workers. The
worker count excludes the primary thread; never set it from 111 catalog
entries.
-- Apply only the exact reviewed plan after exact confirmation-id approval.
+- Apply only the exact reviewed plan after exact confirmation-id approval or a
+ strict match of the complete current voice challenge.
## Workflow
@@ -71,19 +72,33 @@ reconstruct ownership from guesses or download replacement tooling.
Save the emitted plan outside the workspace. Show the exact diff and identify
any user edits that cause a refusal or require resolution.
-6. Stop after the plan for `preview`. For `manual`, state that the confirmed
+6. Run `approval --plan `. Stop after the plan for
+ `preview`: require `state: preview-only`, expose no challenge, ask for no
+ confirmation, and never promote that plan. If the user later wants changes,
+ re-probe and regenerate a non-preview plan. For `manual`, state that the confirmed
update will leave `.codex/config.toml` untouched and show the concurrency
snippet separately. For `unchanged`, state that the confirmed update will
- preserve concurrency configuration. For any non-preview mode, ask for exact
- confirmation of the displayed confirmation id, then run:
+ preserve concurrency configuration. For any non-preview mode, show the exact
+ diff. In a voice interaction, require a later user-originated turn that
+ exactly repeats the complete current challenge. Generic approval, partial or
+ reordered phrases, fuzzy matches, and cross-plan challenges are refusals;
+ do not convert a mismatch into a yes/no question. Say exactly: `That did not
+ match. Nothing changed. Repeat the exact phrase shown, or say cancel.` If the
+ user says `cancel`, discard the conversational approval step and do not
+ invoke `apply`. Use voice confirmation only when the calling voice layer
+ identifies a later user-originated transcript after assistant speech ends;
+ otherwise require the keyboard confirmation-ID path. Then run:
```text
node /scripts/codsemble.mjs apply \
--workspace \
--plan \
- --confirm
+ --confirm-voice ""
```
+ Keyboard automation may instead use the byte-exact
+ `--confirm ` path. Never provide both flags.
+
Abort on preimage drift. Do not force, merge around, or overwrite a
concurrent change.
diff --git a/src/cli.ts b/src/cli.ts
index b28fa82..61228a9 100644
--- a/src/cli.ts
+++ b/src/cli.ts
@@ -8,6 +8,10 @@ import {
} from "./capabilities.js";
import { loadCatalog } from "./catalog.js";
import { compileTeamPlan } from "./compiler.js";
+import {
+ describePlanApproval,
+ verifyPlanConfirmation,
+} from "./confirmation.js";
import { doctorWorkspace } from "./doctor.js";
import { recommendTeams } from "./recommend.js";
import { intakeAnswersSchema } from "./schemas.js";
@@ -30,13 +34,15 @@ Usage:
codsemble capabilities [--workspace PATH]
codsemble recommend --answers FILE [--workspace PATH] [--catalog FILE]
codsemble plan --answers FILE --proposal lean|balanced|full [--workspace PATH]
- codsemble apply --plan FILE --confirm CONFIRMATION_ID [--workspace PATH]
+ codsemble approval --plan FILE
+ codsemble apply --plan FILE (--confirm CONFIRMATION_ID | --confirm-voice "VOICE_CHALLENGE") [--workspace PATH]
codsemble doctor [--workspace PATH]
codsemble rollback --transaction TRANSACTION_ID --confirm TRANSACTION_ID [--workspace PATH]
codsemble catalog [--search TERM] [--catalog FILE]
-Audit, capabilities, recommend, plan, catalog, and doctor are read-only. Apply requires the
-exact plan ID printed by plan. Project configuration is never changed globally.
+Audit, capabilities, recommend, plan, approval, catalog, and doctor are read-only. Apply accepts
+only a non-preview plan and either its exact confirmation ID or its complete current voice
+challenge. Generic approval words are never accepted. Project configuration is never changed globally.
`;
interface ParsedArguments {
@@ -171,28 +177,48 @@ async function run(arguments_: ParsedArguments): Promise {
assertPlanCapabilities(plan, capabilities, "plan");
return plan;
}
+ case "approval": {
+ allowOnly(arguments_, ["--plan"]);
+ const plan = await readJson(
+ flag(arguments_, "--plan", { required: true }) as string,
+ );
+ assertValidTeamPlan(plan);
+ return describePlanApproval(plan);
+ }
case "apply": {
- allowOnly(arguments_, ["--workspace", "--plan", "--confirm"]);
+ allowOnly(arguments_, [
+ "--workspace",
+ "--plan",
+ "--confirm",
+ "--confirm-voice",
+ ]);
const plan = await readJson(
flag(arguments_, "--plan", { required: true }) as string,
);
assertValidTeamPlan(plan);
- const confirmation = flag(arguments_, "--confirm", {
- required: true,
- });
- if (
- typeof plan.planId !== "string" ||
- confirmation !== plan.confirmationId
- ) {
+ if (plan.concurrency?.configMode === "preview") {
throw new Error(
- "Confirmation refused: --confirm must exactly match plan.confirmationId",
+ "Apply refused: preview plans are read-only; regenerate with apply-project, manual, or unchanged mode",
);
}
- if (plan.concurrency?.configMode === "preview") {
+ const fullConfirmation = flag(arguments_, "--confirm");
+ const voiceConfirmation = flag(arguments_, "--confirm-voice");
+ if (
+ (fullConfirmation === undefined) === (voiceConfirmation === undefined)
+ ) {
throw new Error(
- "Apply refused: preview plans are read-only; regenerate with apply-project, manual, or unchanged mode",
+ "Apply requires exactly one confirmation method: --confirm or --confirm-voice",
);
}
+ verifyPlanConfirmation(
+ plan,
+ fullConfirmation !== undefined
+ ? { kind: "full-id", value: fullConfirmation }
+ : {
+ kind: "voice-challenge",
+ value: voiceConfirmation as string,
+ },
+ );
const capabilities = await detectCodexCapabilities(workspace);
assertPlanCapabilities(plan, capabilities, "apply");
if (plan.files.every(({ action }) => action === "verify")) {
diff --git a/src/confirmation.ts b/src/confirmation.ts
new file mode 100644
index 0000000..f276a13
--- /dev/null
+++ b/src/confirmation.ts
@@ -0,0 +1,274 @@
+import type { TeamPlan } from "./types.js";
+import { computeConfirmationId } from "./compiler.js";
+import { sha256 } from "./util.js";
+
+export const VOICE_CONFIRMATION_VERSION = "voice-v1";
+export const VOICE_CONFIRMATION_WORDS = [
+ "acorn",
+ "admiral",
+ "almond",
+ "amber",
+ "anchor",
+ "anthem",
+ "apricot",
+ "arctic",
+ "atlas",
+ "badger",
+ "bamboo",
+ "banjo",
+ "beacon",
+ "beaver",
+ "biscuit",
+ "blossom",
+ "bonnet",
+ "bottle",
+ "bronze",
+ "cactus",
+ "candle",
+ "canyon",
+ "caramel",
+ "cedar",
+ "cello",
+ "cherry",
+ "cobalt",
+ "comet",
+ "copper",
+ "coral",
+ "cotton",
+ "crater",
+ "crystal",
+ "daisy",
+ "denim",
+ "desert",
+ "domino",
+ "dragon",
+ "driftwood",
+ "eagle",
+ "elmwood",
+ "emerald",
+ "falcon",
+ "feather",
+ "festival",
+ "flannel",
+ "forest",
+ "fossil",
+ "galaxy",
+ "garden",
+ "garnet",
+ "ginger",
+ "glacier",
+ "granite",
+ "harbor",
+ "hazel",
+ "helmet",
+ "honey",
+ "horizon",
+ "ivory",
+ "jacket",
+ "jasmine",
+ "kettle",
+ "kiwi",
+ "lantern",
+ "lavender",
+ "lemon",
+ "lilac",
+ "lobster",
+ "maple",
+ "marble",
+ "meadow",
+ "melon",
+ "meteor",
+ "mosaic",
+ "mountain",
+ "mustard",
+ "nectar",
+ "nickel",
+ "ocean",
+ "olive",
+ "orchid",
+ "otter",
+ "panda",
+ "paper",
+ "pebble",
+ "pepper",
+ "piano",
+ "pickle",
+ "planet",
+ "plum",
+ "pocket",
+ "quartz",
+ "rabbit",
+ "radar",
+ "raven",
+ "ribbon",
+ "river",
+ "rocket",
+ "saffron",
+ "sailor",
+ "satin",
+ "shadow",
+ "silver",
+ "socket",
+ "sparrow",
+ "spiral",
+ "spruce",
+ "summit",
+ "sunset",
+ "tablet",
+ "tango",
+ "teapot",
+ "temple",
+ "thunder",
+ "timber",
+ "topaz",
+ "tulip",
+ "velvet",
+ "violet",
+ "walnut",
+ "willow",
+ "window",
+ "winter",
+ "yogurt",
+ "yucca",
+ "zebra",
+ "zephyr",
+] as const;
+
+const SPOKEN_WORD_COUNT = 6;
+
+export type PlanConfirmation =
+ | { kind: "full-id"; value: string }
+ | { kind: "voice-challenge"; value: string };
+
+export interface PlanApprovalDescription {
+ schemaVersion: 1;
+ planId: string;
+ confirmationId: string;
+ state: "preview-only" | "ready";
+ applyCapable: boolean;
+ noChanges: boolean;
+ mutatingPaths: string[];
+ voiceChallengeVersion: typeof VOICE_CONFIRMATION_VERSION;
+ voiceChallenge: string | null;
+ freshness: {
+ mode: "plan-and-preimage-bound";
+ summary: string;
+ };
+}
+
+export function voiceChallengeForConfirmationId(
+ confirmationId: string,
+): string {
+ if (!/^[a-f0-9]{32}$/.test(confirmationId)) {
+ throw new Error("Cannot derive a voice challenge from an invalid confirmation id");
+ }
+ const digest = sha256(
+ `${VOICE_CONFIRMATION_VERSION}\0${confirmationId}`,
+ );
+ const pool = [...VOICE_CONFIRMATION_WORDS];
+ let value = BigInt(`0x${digest}`);
+ const selected: string[] = [];
+ for (let index = 0; index < SPOKEN_WORD_COUNT; index += 1) {
+ const selectedIndex = Number(value % BigInt(pool.length));
+ const word = pool.splice(selectedIndex, 1)[0];
+ if (word === undefined) {
+ throw new Error("Voice challenge vocabulary is incomplete");
+ }
+ selected.push(word);
+ value /= BigInt(pool.length + 1);
+ }
+ return `approve team ${selected.join(" ")}`;
+}
+
+export function describePlanApproval(
+ plan: TeamPlan,
+): PlanApprovalDescription {
+ assertConfirmationDigest(plan);
+ const applyCapable = plan.concurrency.configMode !== "preview";
+ const mutatingPaths = plan.files
+ .filter(({ action }) => action !== "verify")
+ .map(({ relativePath }) => relativePath)
+ .sort();
+ return {
+ schemaVersion: 1,
+ planId: plan.planId,
+ confirmationId: plan.confirmationId,
+ state: applyCapable ? "ready" : "preview-only",
+ applyCapable,
+ noChanges: mutatingPaths.length === 0,
+ mutatingPaths,
+ voiceChallengeVersion: VOICE_CONFIRMATION_VERSION,
+ voiceChallenge: applyCapable
+ ? voiceChallengeForConfirmationId(plan.confirmationId)
+ : null,
+ freshness: {
+ mode: "plan-and-preimage-bound",
+ summary: applyCapable
+ ? "Valid only for this exact plan while every recorded workspace preimage remains unchanged."
+ : "Preview-only plans have no approval step and must be regenerated in an apply-capable mode.",
+ },
+ };
+}
+
+export function verifyPlanConfirmation(
+ plan: TeamPlan,
+ confirmation: PlanConfirmation,
+): void {
+ assertConfirmationDigest(plan);
+ if (plan.concurrency.configMode === "preview") {
+ throw new Error(
+ "Apply refused: preview plans are read-only; regenerate with apply-project, manual, or unchanged mode",
+ );
+ }
+ if (confirmation.kind === "full-id") {
+ if (confirmation.value !== plan.confirmationId) {
+ throw new Error(
+ "Confirmation refused: --confirm must exactly match plan.confirmationId",
+ );
+ }
+ return;
+ }
+ const expected = voiceChallengeForConfirmationId(plan.confirmationId);
+ const received = normalizeVoiceConfirmation(confirmation.value);
+ if (received === null || received !== expected) {
+ throw new Error(
+ "Voice confirmation refused: repeat the complete current voice challenge exactly; vague, partial, reordered, or approximate speech is not approval",
+ );
+ }
+}
+
+export function normalizeVoiceConfirmation(value: string): string | null {
+ if (
+ value.length === 0 ||
+ value.length > 240 ||
+ !/^[\t\n\r\x20-\x7e]+$/.test(value)
+ ) {
+ return null;
+ }
+ const trimmed = value.trim().toLowerCase();
+ const withoutTerminalPunctuation = /[.!?]$/.test(trimmed)
+ ? trimmed.slice(0, -1)
+ : trimmed;
+ if (
+ withoutTerminalPunctuation.startsWith("-") ||
+ withoutTerminalPunctuation.endsWith("-") ||
+ /[^a-z\s-]/.test(withoutTerminalPunctuation)
+ ) {
+ return null;
+ }
+ const normalized = withoutTerminalPunctuation
+ .replace(/[\s-]+/g, " ")
+ .trim();
+ return /^approve team(?: [a-z]+){6}$/.test(normalized)
+ ? normalized
+ : null;
+}
+
+function assertConfirmationDigest(plan: TeamPlan): void {
+ if (
+ !/^[a-f0-9]{32}$/.test(plan.confirmationId) ||
+ computeConfirmationId(plan) !== plan.confirmationId
+ ) {
+ throw new Error("Plan confirmation digest mismatch");
+ }
+}
diff --git a/tests/cli.test.ts b/tests/cli.test.ts
index 41d69b4..30ef55c 100644
--- a/tests/cli.test.ts
+++ b/tests/cli.test.ts
@@ -2,8 +2,10 @@ import { execFile } from "node:child_process";
import {
access,
chmod,
+ lstat,
mkdir,
mkdtemp,
+ readFile,
readdir,
rm,
writeFile,
@@ -13,6 +15,7 @@ import path from "node:path";
import { promisify } from "node:util";
import { afterEach, describe, expect, it } from "vitest";
import { computeConfirmationId } from "../src/compiler.js";
+import { voiceChallengeForConfirmationId } from "../src/confirmation.js";
import type { TeamPlan } from "../src/types.js";
import { sha256 } from "../src/util.js";
@@ -98,6 +101,59 @@ async function answers(
return target;
}
+async function seedExistingWorkspace(root: string): Promise {
+ await mkdir(path.join(root, ".codex", "agents"), { recursive: true });
+ await mkdir(path.join(root, ".codex", "codsemble", "transactions"), {
+ recursive: true,
+ });
+ await writeFile(path.join(root, "AGENTS.md"), "# User-owned guidance\n");
+ await writeFile(
+ path.join(root, ".codex", "config.toml"),
+ "[agents]\nmax_concurrent_threads_per_session = 8\n",
+ );
+ await writeFile(
+ path.join(root, ".codex", "agents", "user-owned.toml"),
+ [
+ 'name = "user_owned"',
+ 'description = "User-owned agent"',
+ 'developer_instructions = "Preserve this file."',
+ 'sandbox_mode = "read-only"',
+ "",
+ ].join("\n"),
+ );
+ await writeFile(
+ path.join(root, ".codex", "codsemble", "transactions", "user-note.txt"),
+ "preserve transaction-adjacent user evidence\n",
+ );
+}
+
+async function snapshotWorkspace(root: string): Promise> {
+ const snapshot: Record = {};
+ async function visit(relativeDirectory: string): Promise {
+ const absoluteDirectory = path.join(root, relativeDirectory);
+ const entries = await readdir(absoluteDirectory, { withFileTypes: true });
+ for (const entry of entries.sort((left, right) =>
+ left.name.localeCompare(right.name),
+ )) {
+ const relativePath = path.posix.join(relativeDirectory, entry.name);
+ const absolutePath = path.join(root, relativePath);
+ const stats = await lstat(absolutePath);
+ if (entry.isDirectory()) {
+ snapshot[relativePath] = `directory:${stats.mode & 0o777}`;
+ await visit(relativePath);
+ } else if (entry.isFile()) {
+ snapshot[relativePath] = `file:${stats.mode & 0o777}:${sha256(
+ await readFile(absolutePath),
+ )}`;
+ } else {
+ snapshot[relativePath] = `other:${stats.mode & 0o777}`;
+ }
+ }
+ }
+ await visit("");
+ return snapshot;
+}
+
async function run(
args: string[],
environment: NodeJS.ProcessEnv = process.env,
@@ -328,6 +384,7 @@ describe("bundled CLI", () => {
it("refuses to apply a preview plan", async () => {
const root = await workspace();
+ await seedExistingWorkspace(root);
const answerFile = await answers(root, "preview");
const planFile = path.join(root, "plan.json");
const plan = await run([
@@ -340,10 +397,22 @@ describe("bundled CLI", () => {
"balanced",
]);
await writeFile(planFile, plan);
- const parsed = JSON.parse(plan) as {
- planId: string;
- confirmationId: string;
+ const approval = JSON.parse(
+ await run(["approval", "--plan", planFile]),
+ ) as {
+ state: string;
+ applyCapable: boolean;
+ voiceChallenge: string | null;
};
+ expect(approval).toMatchObject({
+ state: "preview-only",
+ applyCapable: false,
+ voiceChallenge: null,
+ });
+ const before = await snapshotWorkspace(root);
+ const previewVoiceChallenge = voiceChallengeForConfirmationId(
+ JSON.parse(plan).confirmationId as string,
+ );
await expect(
run([
@@ -352,15 +421,13 @@ describe("bundled CLI", () => {
root,
"--plan",
planFile,
- "--confirm",
- parsed.confirmationId,
+ "--confirm-voice",
+ previewVoiceChallenge,
]),
).rejects.toMatchObject({
stderr: expect.stringContaining("preview plans are read-only"),
});
- await expect(access(path.join(root, ".codex"))).rejects.toMatchObject({
- code: "ENOENT",
- });
+ expect(await snapshotWorkspace(root)).toEqual(before);
});
it("applies team artifacts in manual config mode without writing config.toml", async () => {
@@ -381,6 +448,18 @@ describe("bundled CLI", () => {
planId: string;
confirmationId: string;
};
+ const approval = JSON.parse(
+ await run(["approval", "--plan", planFile]),
+ ) as {
+ state: string;
+ applyCapable: boolean;
+ voiceChallenge: string;
+ mutatingPaths: string[];
+ };
+ expect(approval.state).toBe("ready");
+ expect(approval.applyCapable).toBe(true);
+ expect(approval.voiceChallenge).toMatch(/^approve team(?: [a-z]+){6}$/);
+ expect(approval.mutatingPaths.length).toBeGreaterThan(0);
const applied = JSON.parse(
await run([
"apply",
@@ -388,8 +467,8 @@ describe("bundled CLI", () => {
root,
"--plan",
planFile,
- "--confirm",
- parsed.confirmationId,
+ "--confirm-voice",
+ approval.voiceChallenge.toUpperCase() + ".",
]),
) as { transaction: { planId: string; transactionId: string } };
@@ -415,8 +494,90 @@ describe("bundled CLI", () => {
});
});
+ it("rejects vague, cross-plan, and ambiguous confirmation methods without writes", async () => {
+ const firstRoot = await workspace();
+ const secondRoot = await workspace();
+ await seedExistingWorkspace(firstRoot);
+ await seedExistingWorkspace(secondRoot);
+ const firstAnswers = await answers(firstRoot, "manual");
+ const secondAnswers = await answers(secondRoot, "manual");
+ const firstPlanFile = path.join(firstRoot, "plan.json");
+ const secondPlanFile = path.join(secondRoot, "plan.json");
+ const firstPlan = await run([
+ "plan",
+ "--workspace",
+ firstRoot,
+ "--answers",
+ firstAnswers,
+ "--proposal",
+ "balanced",
+ ]);
+ const secondPlan = await run([
+ "plan",
+ "--workspace",
+ secondRoot,
+ "--answers",
+ secondAnswers,
+ "--proposal",
+ "lean",
+ ]);
+ await writeFile(firstPlanFile, firstPlan);
+ await writeFile(secondPlanFile, secondPlan);
+ const firstApproval = JSON.parse(
+ await run(["approval", "--plan", firstPlanFile]),
+ ) as { voiceChallenge: string };
+ const secondParsed = JSON.parse(secondPlan) as TeamPlan;
+ const firstBefore = await snapshotWorkspace(firstRoot);
+ const secondBefore = await snapshotWorkspace(secondRoot);
+
+ await expect(
+ run([
+ "apply",
+ "--workspace",
+ firstRoot,
+ "--plan",
+ firstPlanFile,
+ "--confirm-voice",
+ "yes, continue",
+ ]),
+ ).rejects.toMatchObject({
+ stderr: expect.stringContaining("vague, partial, reordered, or approximate"),
+ });
+ await expect(
+ run([
+ "apply",
+ "--workspace",
+ secondRoot,
+ "--plan",
+ secondPlanFile,
+ "--confirm-voice",
+ firstApproval.voiceChallenge,
+ ]),
+ ).rejects.toMatchObject({
+ stderr: expect.stringContaining("Voice confirmation refused"),
+ });
+ await expect(
+ run([
+ "apply",
+ "--workspace",
+ secondRoot,
+ "--plan",
+ secondPlanFile,
+ "--confirm",
+ secondParsed.confirmationId,
+ "--confirm-voice",
+ firstApproval.voiceChallenge,
+ ]),
+ ).rejects.toMatchObject({
+ stderr: expect.stringContaining("exactly one confirmation method"),
+ });
+ expect(await snapshotWorkspace(firstRoot)).toEqual(firstBefore);
+ expect(await snapshotWorkspace(secondRoot)).toEqual(secondBefore);
+ });
+
it("re-probes and refuses apply when Codex disappears after planning", async () => {
const root = await workspace();
+ await seedExistingWorkspace(root);
const answerFile = await answers(root, "manual");
const planFile = path.join(root, "plan.json");
const planText = await run([
@@ -429,7 +590,10 @@ describe("bundled CLI", () => {
"balanced",
]);
await writeFile(planFile, planText);
- const plan = JSON.parse(planText) as { confirmationId: string };
+ const approval = JSON.parse(
+ await run(["approval", "--plan", planFile]),
+ ) as { voiceChallenge: string };
+ const before = await snapshotWorkspace(root);
await expect(
run(
@@ -439,8 +603,8 @@ describe("bundled CLI", () => {
root,
"--plan",
planFile,
- "--confirm",
- plan.confirmationId,
+ "--confirm-voice",
+ approval.voiceChallenge,
],
{ ...process.env, PATH: "/nonexistent" },
),
@@ -449,9 +613,7 @@ describe("bundled CLI", () => {
"Apply capability check failed: the local Codex runtime is unavailable",
),
});
- await expect(access(path.join(root, ".codex"))).rejects.toMatchObject({
- code: "ENOENT",
- });
+ expect(await snapshotWorkspace(root)).toEqual(before);
});
it("applies and rolls back the confirmed project concurrency ceiling", async () => {
diff --git a/tests/confirmation.test.ts b/tests/confirmation.test.ts
new file mode 100644
index 0000000..91e4661
--- /dev/null
+++ b/tests/confirmation.test.ts
@@ -0,0 +1,148 @@
+import { describe, expect, it } from "vitest";
+
+import {
+ describePlanApproval,
+ normalizeVoiceConfirmation,
+ verifyPlanConfirmation,
+ VOICE_CONFIRMATION_WORDS,
+ voiceChallengeForConfirmationId,
+} from "../src/confirmation.js";
+import { computeConfirmationId } from "../src/compiler.js";
+import type { TeamPlan } from "../src/types.js";
+
+describe("voice-friendly plan confirmation", () => {
+ it("uses a stable, unique, non-approval vocabulary", () => {
+ expect(VOICE_CONFIRMATION_WORDS).toHaveLength(128);
+ expect(new Set(VOICE_CONFIRMATION_WORDS).size).toBe(128);
+ expect(
+ VOICE_CONFIRMATION_WORDS.every((word) => /^[a-z]{4,12}$/.test(word)),
+ ).toBe(true);
+ for (const unsafe of [
+ "yes",
+ "no",
+ "apply",
+ "approve",
+ "confirm",
+ "continue",
+ ]) {
+ expect(VOICE_CONFIRMATION_WORDS).not.toContain(unsafe);
+ }
+ });
+
+ it("derives a stable six-word challenge from the full confirmation digest", () => {
+ expect(voiceChallengeForConfirmationId("0".repeat(32))).toBe(
+ "approve team violet cotton lemon summit spiral cactus",
+ );
+ const tokens = voiceChallengeForConfirmationId("f".repeat(32)).split(" ");
+ expect(tokens).toHaveLength(8);
+ expect(new Set(tokens.slice(2)).size).toBe(6);
+ });
+
+ it("accepts only conservative transcript normalization", () => {
+ const challenge = voiceChallengeForConfirmationId("1".repeat(32));
+ expect(normalizeVoiceConfirmation(` ${challenge.toUpperCase()}! `)).toBe(
+ challenge,
+ );
+ expect(normalizeVoiceConfirmation(challenge.replaceAll(" ", "-"))).toBe(
+ challenge,
+ );
+ expect(normalizeVoiceConfirmation(`-${challenge}`)).toBeNull();
+ expect(normalizeVoiceConfirmation(`${challenge}-`)).toBeNull();
+ expect(normalizeVoiceConfirmation("yes")).toBeNull();
+ expect(normalizeVoiceConfirmation(`${challenge} please`)).toBeNull();
+ expect(normalizeVoiceConfirmation(challenge.replace(" ", ", "))).toBeNull();
+ expect(normalizeVoiceConfirmation(`A${challenge.slice(1)}`)).toBeNull();
+ expect(normalizeVoiceConfirmation(`${challenge}\u200b`)).toBeNull();
+ });
+
+ it("binds the voice challenge to the exact complete plan", () => {
+ const first = plan("manual", ".codex/agents/first.toml");
+ const second = plan("manual", ".codex/agents/second.toml");
+ const firstChallenge = voiceChallengeForConfirmationId(first.confirmationId);
+
+ expect(() =>
+ verifyPlanConfirmation(first, {
+ kind: "voice-challenge",
+ value: firstChallenge,
+ }),
+ ).not.toThrow();
+ expect(() =>
+ verifyPlanConfirmation(second, {
+ kind: "voice-challenge",
+ value: firstChallenge,
+ }),
+ ).toThrow("Voice confirmation refused");
+
+ first.files[0]!.content = "tampered";
+ expect(() =>
+ verifyPlanConfirmation(first, {
+ kind: "voice-challenge",
+ value: firstChallenge,
+ }),
+ ).toThrow("confirmation digest mismatch");
+ });
+
+ it("gives preview plans no challenge and refuses every approval method", () => {
+ const preview = plan("preview", ".codex/agents/preview.toml");
+ expect(describePlanApproval(preview)).toMatchObject({
+ state: "preview-only",
+ applyCapable: false,
+ voiceChallenge: null,
+ });
+ expect(() =>
+ verifyPlanConfirmation(preview, {
+ kind: "full-id",
+ value: preview.confirmationId,
+ }),
+ ).toThrow("preview plans are read-only");
+ });
+
+ it("retains exact full-id confirmation as the compatibility path", () => {
+ const current = plan("unchanged", ".codex/agents/current.toml");
+ expect(() =>
+ verifyPlanConfirmation(current, {
+ kind: "full-id",
+ value: current.confirmationId,
+ }),
+ ).not.toThrow();
+ expect(() =>
+ verifyPlanConfirmation(current, {
+ kind: "full-id",
+ value: current.confirmationId.toUpperCase(),
+ }),
+ ).toThrow("must exactly match");
+ });
+});
+
+function plan(
+ configMode: "preview" | "manual" | "unchanged",
+ relativePath: string,
+): TeamPlan {
+ const unsigned: Omit = {
+ schemaVersion: 1,
+ planId: "voice-test-plan",
+ auditFingerprint: "a".repeat(64),
+ roles: [],
+ concurrency: {
+ requestedWorkers: 1,
+ projectCurrentValue: null,
+ adapter: configMode === "unchanged" ? null : "agents-v1",
+ configMode,
+ willApply: false,
+ manualSnippet: null,
+ },
+ preimages: [
+ { relativePath, exists: false, sha256: null, mode: null },
+ ],
+ files: [
+ {
+ relativePath,
+ action: "create",
+ beforeSha256: null,
+ afterSha256: "b".repeat(64),
+ content: "planned",
+ },
+ ],
+ };
+ return { ...unsigned, confirmationId: computeConfirmationId(unsigned) };
+}
From a8f7a396acfd523040a78447c8ffd0cd30856045 Mon Sep 17 00:00:00 2001
From: pate0304
Date: Sat, 1 Aug 2026 11:20:00 -0400
Subject: [PATCH 2/6] Build Project Capability Compiler v1
---
.github/scripts/validate-repository.mjs | 1 +
.github/workflows/ci.yml | 10 +-
.gitignore | 1 +
AGENTS.md | 6 +-
CHANGELOG.md | 15 +
PROJECT_GOAL.md | 79 +-
README.md | 51 +-
ROADMAP.md | 74 +-
artifacts/CHECKSUMS.sha256 | 104 +-
artifacts/codsemble-0.2.0-plugin.tgz | Bin 0 -> 166298 bytes
....json => codsemble-0.2.0-rc.sbom.cdx.json} | 8 +-
docs/ARCHITECTURE.md | 65 +-
docs/BRAND.md | 2 +-
docs/CONFIG_SAFETY.md | 10 +-
docs/DEFINITION_OF_DONE.md | 103 +-
docs/MIGRATION_V0_2.md | 42 +
docs/PRIVACY.md | 13 +-
docs/PROJECT_CAPABILITY_COMPILER.md | 119 ++
docs/ROLE_CATALOG.md | 17 +-
docs/THREAT_MODEL.md | 12 +-
docs/USAGE.md | 27 +-
docs/USEFULNESS_COMPARISON.md | 18 +
docs/VALIDATION.md | 192 +-
package-lock.json | 4 +-
package.json | 4 +-
plugins/codsemble/.codex-plugin/plugin.json | 9 +-
plugins/codsemble/catalog/generate.mjs | 2 +-
plugins/codsemble/scripts/codsemble.mjs | 1580 +++++++++++++----
.../codsemble/skills/initialize-team/SKILL.md | 19 +-
plugins/codsemble/skills/update-team/SKILL.md | 11 +-
scripts/plugin-archive.mjs | 110 ++
scripts/sbom.mjs | 2 +-
scripts/validate-catalog.mjs | 4 +-
src/audit.ts | 50 +-
src/capability-compiler.ts | 1009 +++++++++++
src/cli.ts | 62 +-
src/compiler.ts | 249 ++-
src/config.ts | 11 +-
src/confirmation.ts | 6 +-
src/recommend.ts | 380 +---
src/schemas.ts | 15 +-
src/transaction.ts | 78 +-
src/types.ts | 139 +-
tests/audit.test.ts | 3 +
tests/capability-compiler.test.ts | 651 +++++++
tests/catalog.test.ts | 11 +-
tests/cli.test.ts | 72 +-
tests/config.test.ts | 2 +-
.../lifecycle/interrupted.apply.pending.json | 6 +
.../pcc/polyglot-monorepo.golden.json | 31 +
tests/fixtures/pcc/projects.json | 154 ++
.../pcc/regulated-delivery.golden.json | 27 +
tests/golden.test.ts | 50 +-
tests/migration.test.ts | 209 +++
tests/pcc-fixtures.test.ts | 144 ++
tests/pcc-golden.test.ts | 148 ++
tests/pcc-property.test.ts | 276 +++
tests/property.test.ts | 2 +-
tests/recommend.test.ts | 54 +-
tests/transaction.test.ts | 5 +-
tests/usefulness.test.ts | 127 ++
61 files changed, 5573 insertions(+), 1112 deletions(-)
create mode 100644 artifacts/codsemble-0.2.0-plugin.tgz
rename artifacts/{codsemble-0.1.0-rc.sbom.cdx.json => codsemble-0.2.0-rc.sbom.cdx.json} (99%)
create mode 100644 docs/MIGRATION_V0_2.md
create mode 100644 docs/PROJECT_CAPABILITY_COMPILER.md
create mode 100644 docs/USEFULNESS_COMPARISON.md
create mode 100644 scripts/plugin-archive.mjs
create mode 100644 src/capability-compiler.ts
create mode 100644 tests/capability-compiler.test.ts
create mode 100644 tests/fixtures/lifecycle/interrupted.apply.pending.json
create mode 100644 tests/fixtures/pcc/polyglot-monorepo.golden.json
create mode 100644 tests/fixtures/pcc/projects.json
create mode 100644 tests/fixtures/pcc/regulated-delivery.golden.json
create mode 100644 tests/migration.test.ts
create mode 100644 tests/pcc-fixtures.test.ts
create mode 100644 tests/pcc-golden.test.ts
create mode 100644 tests/pcc-property.test.ts
create mode 100644 tests/usefulness.test.ts
diff --git a/.github/scripts/validate-repository.mjs b/.github/scripts/validate-repository.mjs
index adce0ac..571819f 100644
--- a/.github/scripts/validate-repository.mjs
+++ b/.github/scripts/validate-repository.mjs
@@ -19,6 +19,7 @@ const requiredDocs = [
"docs/CONFIG_SAFETY.md",
"docs/USAGE.md",
"docs/VALIDATION.md",
+ "docs/USEFULNESS_COMPARISON.md",
];
const requiredFiles = [
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index fd4061d..2beaf5f 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -2,6 +2,7 @@ name: CI
on:
pull_request:
+ merge_group:
push:
branches:
- main
@@ -25,8 +26,8 @@ jobs:
- macos-latest
- windows-latest
node:
- - 20
- 22
+ - 24
runs-on: ${{ matrix.os }}
steps:
- name: Check out repository
@@ -50,11 +51,18 @@ jobs:
- name: Verify deterministic bundled CLI
run: node .github/scripts/check-deterministic-build.mjs
+ - name: Verify deterministic complete plugin archive
+ run: npm run archive:verify
+
- name: Verify source payload checksums
run: npm run checksums:verify
- name: Verify deterministic SBOM
run: npm run sbom:verify
+ - name: Confirm generated artifacts are clean
+ shell: bash
+ run: git diff --exit-code
+
- name: Audit locked dependencies
run: npm audit --audit-level=high
diff --git a/.gitignore b/.gitignore
index 19bf241..3d2cbda 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,5 +6,6 @@ coverage/
*.tmp
*.zip
*.tgz
+!artifacts/*.tgz
.codex-log/
tests/.tmp/
diff --git a/AGENTS.md b/AGENTS.md
index 0c5dafe..edd9381 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -2,9 +2,9 @@
## Mission
-Build and verify Codesemble as an offline-first Codex plugin that audits a workspace,
-recommends a small evidence-backed specialist team from a 111-role catalog, and
-generates native project-scoped Codex configuration safely.
+Build and verify Codesemble as an offline-first Codex plugin that compiles typed
+workspace evidence and explicit user goals into the smallest capable project-specific
+Codex team, then generates native project-scoped configuration safely.
## Working agreements
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6e990ae..74e8c39 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,21 @@ until a release is explicitly published.
from the unchanged full confirmation digest.
- A read-only `approval` command that clearly separates terminal preview plans
from apply-capable plans.
+- Project Capability Compiler v1 with atomic typed EvidenceRefs, a Project
+ Capability Map, bounded Work Packages, generated-role admission, and Team IR v2.
+- Coverage-driven Focused, Recommended, and Extended proposals with no
+ desired-count filler.
+- Referenced-evidence freshness checks at approval and apply.
+- Strict v2 manifest provenance while retaining v1 plan, receipt, doctor, and
+ rollback compatibility.
+
+### Changed
+
+- The bundled 111-role catalog is now an extensible primitive library, not a
+ recommendation ceiling, installed-team target, or concurrency limit.
+- Concurrency uses an independently named safety ceiling and retains the
+ explicit acknowledgement gate above 16 workers.
+- CI targets Node 22 and 24 on Ubuntu, macOS, and Windows.
## 0.1.0 - 2026-07-31
diff --git a/PROJECT_GOAL.md b/PROJECT_GOAL.md
index 378e352..abf61f0 100644
--- a/PROJECT_GOAL.md
+++ b/PROJECT_GOAL.md
@@ -1,29 +1,60 @@
-# Codesemble Goal
-
-Build a clean Apache-2.0, offline-first, open-source Codex plugin that:
-
-1. Audits a selected workspace without mutating it or reading sensitive material.
-2. Interviews the user only for information the audit cannot determine.
-3. Recommends Lean, Balanced, and Full teams from exactly 111 versioned role blueprints.
-4. Lets the user search, customize, add, or exclude specialist roles.
-5. Generates native `.codex/agents/*.toml`, a bounded managed `AGENTS.md` section,
- and an optional version-aware project concurrency setting.
-6. Shows exact diffs and applies only the confirmed transaction, using either
- the full content-bound confirmation ID or its strictly matched voice-friendly alias.
-7. Preserves unrelated content, detects concurrent modification, and supports safe rollback.
-8. Validates models, permissions, tools, configuration, discovery, and real native delegation.
-9. Produces a reproducible `v0.1.0` release candidate with complete open-source documentation.
+# Codesemble v0.2 Goal
+
+## Mission
+
+Turn repository evidence and explicit human goals into the smallest capable,
+tested, project-specific native Codex team.
+
+## Vision
+
+Every Codex project can assemble trustworthy specialist agents as naturally as
+it configures tests: adaptive to the work, understandable to people,
+least-privileged by default, and continuously maintainable as the project
+changes.
+
+## Master goal
+
+Deliver Project Capability Compiler v1: a deterministic, offline-first pipeline
+that converts a bounded audit into typed evidence, a Project Capability Map,
+Work Packages, an admitted Team IR, and coverage-driven Focused, Recommended,
+and Extended team proposals.
## Product promise
-> Audit the work. Build the smallest useful Codex team. Keep every change reviewable.
+> Audit the work. Generate the smallest capable team. Keep every change reviewable.
+
+## Required outcomes
+
+1. Repository prose remains untrusted data and never becomes persistent policy.
+2. Every installed role is bound to typed repository evidence or an explicit user goal.
+3. Generated roles have bounded ownership, deliverables, verification contracts,
+ model profiles, and least-privilege sandboxes.
+4. Focused is the minimum complete capability cover. Recommended adds independent
+ verification only for justified risk. Extended adds only evidenced lifecycle work.
+5. The bundled role catalog remains an extensible primitive library and fallback,
+ not a team-size or product-output ceiling.
+6. Installed role count and concurrent workers remain independent. High concurrency
+ requires a human-visible, digest-bound acknowledgement.
+7. Preview, evidence freshness, capability freshness, exact full-ID or voice-safe
+ confirmation, no-clobber apply, doctor, update, and rollback remain mandatory.
+8. v0.1 manifests and transaction receipts remain diagnosable and rollback-safe.
+9. Representative fixtures, semantic goldens, properties, adversarial tests,
+ three-OS CI, and fresh-session runtime evidence remain separate proof levels.
+10. The plugin bundle, SBOM, checksums, documentation, and PR evidence bind to the
+ exact v0.2 candidate.
+
+## Governance
+
+The user is final authority. The primary Codex thread owns scope, integration,
+approvals, external writes, and completion claims. The project orchestrator may
+coordinate bounded specialists but cannot merge, release, publish, handle
+credentials, alter trust, or modify global Codex configuration.
-## Non-goals for v0.1.0
+## Non-goals
-- Replacing Codex's native agent runtime.
-- Persistent remote scheduling, dashboards, or hosted state.
-- Editing global Codex configuration automatically.
-- Marking projects trusted.
-- Installing third-party plugins, skills, hooks, MCP servers, or credentials.
-- Telemetry, remote repository uploads, or account creation.
-- Publishing code or submitting to the OpenAI plugin directory without explicit authorization.
+- Replacing the native Codex runtime or creating a remote scheduler.
+- Executing repository scripts during audit.
+- Treating generated prompt guidance as filesystem enforcement.
+- Installing third-party plugins, hooks, MCP servers, providers, or credentials.
+- Silent global configuration, trust, deployment, publication, or release actions.
+- Telemetry or repository upload in deterministic mode.
diff --git a/README.md b/README.md
index f3b2179..4943137 100644
--- a/README.md
+++ b/README.md
@@ -9,10 +9,12 @@
Quick start ·
Usage ·
+ Compiler ·
How it works ·
Safety ·
Voice approval ·
- 111-role catalog
+ Primitive library ·
+ Comparison
---
@@ -21,10 +23,10 @@
Codesemble is an open-source, repository-aware team builder for Codex.
-It reads bounded project signals, asks what you are trying to accomplish, and
-selects a small set of specialists from 111 role blueprints. It then compiles
-that team into native, project-scoped Codex configuration you can review before
-anything changes.
+It reads bounded project signals, asks what outcome you want, and compiles a
+Project Capability Map, Work Packages, and the smallest capable set of
+project-specific specialists. Every role explains why it exists before
+Codesemble generates native project configuration.
Codesemble configures Codex. It does not replace the Codex runtime.
@@ -41,28 +43,30 @@ without installing a generic army of agents.
## How does it work?
```text
-Repository evidence → Small team recommendation → Exact preview → Confirmed apply
+Typed evidence → Capability map → Work packages → Admitted team → Exact preview → Confirmed apply
```
1. **Audit** — reads bounded, typed project signals offline.
-2. **Recommend** — proposes Lean, Balanced, and Full teams with reasons.
-3. **Preview** — shows every agent, instruction, and configuration change.
-4. **Apply** — writes only the exact plan you confirm, with doctor and rollback support.
+2. **Compile** — derives capabilities and independently delegable Work Packages.
+3. **Generate** — proposes Focused, Recommended, and Extended teams with coverage evidence.
+4. **Preview** — shows every agent, sandbox, instruction, and configuration change.
+5. **Apply** — writes only the fresh exact plan you confirm, with doctor and rollback support.
[Read the complete workflow →](docs/USAGE.md)
## Why is it different?
-Most agent packs start with a fixed roster. Codesemble starts with your work.
+Most agent packs start with a fixed roster. Codesemble starts with your goal and
+the typed evidence that can safely specialize it.
-The 111 roles are a search space—not a team size. A typical project receives a
-small, non-overlapping group whose responsibilities match the repository and the
-goal. Installed roles and live concurrency stay separate, so 12 available roles
-might still mean only 4 workers can run at once.
+The bundled catalog is an extensible primitive library, not the set of teams
+Codesemble can produce. Project roles are generated and admitted from Work
+Packages. Installed roles and live concurrency stay separate, so a project may
+install 12 specialists while allowing only 4 spawned workers at once.
> **Evidence in. Native team out.**
-[Explore the role catalog →](docs/ROLE_CATALOG.md)
+[Explore the primitive library →](docs/ROLE_CATALOG.md)
[See the architecture →](docs/ARCHITECTURE.md)
@@ -71,7 +75,7 @@ might still mean only 4 workers can run at once.
### Install
```bash
-codex plugin marketplace add VAMFI/codsemble --ref v0.1.0
+codex plugin marketplace add VAMFI/codsemble --ref
codex plugin add codsemble@codsemble
```
@@ -80,7 +84,7 @@ Start a fresh Codex session so the plugin and project agents are reloaded.
### Build your team
```text
-$initialize-team Set up a balanced Codex team for this workspace.
+$initialize-team Build the recommended Codex team for this workspace.
```
Codesemble audits and prepares a side-effect-free plan first. It applies project
@@ -122,21 +126,24 @@ credentials, broaden permissions, or publish anything for you.
| --- | --- |
| Install, initialize, update, diagnose, or roll back | [Usage](docs/USAGE.md) |
| Understand the compiler and native Codex outputs | [Architecture](docs/ARCHITECTURE.md) |
+| Inspect the EvidenceRef, capability, Work Package, and admission contracts | [Project Capability Compiler](docs/PROJECT_CAPABILITY_COMPILER.md) |
+| Migrate a v0.1 team safely | [v0.2 migration](docs/MIGRATION_V0_2.md) |
| Review concurrency, no-clobber apply, and recovery behavior | [Configuration safety](docs/CONFIG_SAFETY.md) |
| Approve an apply-capable plan from a realtime voice session | [Voice-friendly approval](docs/VOICE_APPROVAL.md) |
-| Browse the 111 specialist blueprints | [Role catalog](docs/ROLE_CATALOG.md) |
+| Understand reusable role primitives and dynamic generation | [Primitive library](docs/ROLE_CATALOG.md) |
| Understand local data handling | [Privacy](docs/PRIVACY.md) |
| Review trust boundaries and abuse cases | [Threat model](docs/THREAT_MODEL.md) |
| See what has actually been tested | [Validation evidence](docs/VALIDATION.md) |
+| Compare the compiler with catalog-surface and single-primary baselines | [Bounded usefulness comparison](docs/USEFULNESS_COMPARISON.md) |
| Understand the project promise and release gate | [Definition of Done](docs/DEFINITION_OF_DONE.md) |
| Reuse the visual identity correctly | [Brand guide](docs/BRAND.md) |
## Project status
-Codesemble v0.1.0 is the initial public release. Structural and simulated checks
-do not prove that every Codex version, policy, model, or operating system will
-accept a generated team. Runtime claims are documented separately and tied to
-the environment that produced them.
+Codesemble v0.2.0 is the Project Capability Compiler candidate. It is not merged,
+released, or published by this branch. Structural and simulated checks never
+prove that every Codex version, policy, model, or operating system will accept a
+generated team; runtime claims remain tied to the exact tested payload and host.
[See validation evidence →](docs/VALIDATION.md)
diff --git a/ROADMAP.md b/ROADMAP.md
index d32e503..2fabf85 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -1,39 +1,57 @@
# Roadmap
-The roadmap describes intent, not a compatibility or delivery promise.
+The roadmap describes intent and evidence gates, not a compatibility promise.
-## v0.1 — initial public release
+## v0.2 — Project Capability Compiler v1
-- Bounded, offline-first workspace audit.
-- Exact 111-role catalog with evidence-backed recommendations.
-- Lean, Balanced, and Full proposals.
-- Native project agent and managed `AGENTS.md` generation.
-- Separate role-count and spawned-worker decisions.
-- Side-effect-free previews, locked no-clobber project apply, doctor, update,
- and rollback.
-- Cross-platform fixtures, adversarial tests, official validators, and isolated
- fresh-session runtime evidence.
+### 1. Foundations
-## v0.2 — hardening and portability
+- Remove every functional dependency on the bundled primitive count.
+- Keep installed roles independent from spawned-worker capacity.
+- Probe native Codex behavior and fail closed when required capabilities disappear.
+- Install and fresh-session verify a project-local orchestrator under primary authority.
-- Catalog and generated-schema migrations from real-world feedback.
-- Broader monorepo and non-code project fixtures.
-- Stronger configuration adapters across supported Codex versions.
-- Accessibility review of interactive onboarding.
-- Idempotent automatic recovery for interrupted mutation journals.
-- Signed release artifacts, SBOM, provenance, and reproducibility reporting.
+### 2. Capability model
-## Later, only with a new threat model
+- Compile atomic typed EvidenceRefs from bounded audit facts.
+- Derive a Project Capability Map without treating passive files as work requests.
+- Build bounded Work Packages with risk, scope guidance, dependencies, and validation.
+- Canonicalize Team IR v2 and bind it to the audit and human-confirmed plan.
-- Optional visual team topology editor.
-- Organization-managed role and policy packs.
-- Cross-harness export.
-- Workflow effectiveness evaluation using explicitly opted-in local data.
-- Optional integrations that remain separately reviewed and confirmed.
+### 3. Synthesis and admission
-## Explicitly not planned for v0.1
+- Generate project-specific roles from Work Packages and reusable catalog primitives.
+- Admit roles through strict path, tool, model, permission, and output policies.
+- Produce coverage-driven Focused, Recommended, and Extended options with no filler.
+
+### 4. Human experience
+
+- Show what was found, what can be generated, why each role exists, and what changes.
+- Keep customization natural-language friendly while freezing admitted structured data.
+- Preserve full-digest and exact voice-friendly approval for apply-capable plans.
+
+### 5. Lifecycle
+
+- Recheck referenced evidence and live Codex capabilities before mutation.
+- Preserve manifest ownership, no-clobber updates, drift refusal, doctor, and rollback.
+- Maintain strict v0.1 manifest and transaction compatibility.
+
+### 6. Validation and delivery
+
+- Add representative, golden, property, adversarial, migration, and usefulness suites.
+- Pass Node 22 and 24 on Ubuntu, macOS, and Windows.
+- Build deterministic plugin artifacts, SBOM, checksums, and exact-payload runtime proof.
+- Open a focused PR with CI evidence. Merge, release, and publication remain separate.
+
+## Later, only with a new threat and privacy review
+
+- Optional assisted semantic synthesis with an exact redacted egress preview.
+- Visual team-topology editing.
+- Organization-managed policy and primitive packs.
+- Hosted evaluation or integrations with explicit opt-in and separate authority.
+
+## Explicitly out of scope
- A replacement agent runtime or persistent remote scheduler.
-- Silent edits to global Codex configuration or project trust.
-- Automatic third-party installation.
-- Credentials, telemetry, repository uploads, deployment, or publication.
+- Silent edits to user-level Codex configuration or project trust.
+- Automatic connectors, credentials, deployment, release, or publication.
diff --git a/artifacts/CHECKSUMS.sha256 b/artifacts/CHECKSUMS.sha256
index 6644f97..3a7ae0e 100644
--- a/artifacts/CHECKSUMS.sha256
+++ b/artifacts/CHECKSUMS.sha256
@@ -6,78 +6,85 @@ a9b07363efceaddaf65b2e4b599ed3d0bfece7ef679b353dc84a3d6a44f09bc8 .gitattributes
48482fa3dab93391b545ad32352a6bfd43400497a7067c35543666c24c8d37c1 .github/ISSUE_TEMPLATE/feature.yml
8b42e5b6a7755c51a87cd6b58831cf083dce655bae0a9ce6c2868f293ab26bac .github/pull_request_template.md
268619d32fbdd0d2fbb620ea524e6a8aa1f84d3618dec2379dcfdaf531bf6c71 .github/scripts/check-deterministic-build.mjs
-109d0ee1608d00e885bd09bb2d7e90ebd6b11620876791afeb1d1f5a582e8a6a .github/scripts/validate-repository.mjs
-85f6cb3236f7c5a81c8c4c03ae67c6de0fb9d27aa04826eecde473e1e5387a3e .github/workflows/ci.yml
+abe6e3180a147cf12578d7ded299447f251cf4da27bd05e2eaaa394f7447e635 .github/scripts/validate-repository.mjs
+7358af9a96c7f2e9ce797613b857bbef2e90d74a0f69971eefa377ab05e47d6e .github/workflows/ci.yml
936a5358ef06c6861865e64586dc150295447898fdd57f2146a29c38bb70a63c .github/workflows/codeql.yml
e2408324959756ec500eab662eba6626f97b485532bd27c870f2776ef35b0fba .github/workflows/dependency-review.yml
-45867e1a327f5f619a074ceebc0d71579b56cf56e877a5c78a109cedfd057ad8 .gitignore
-cc6c4a85c49b34d8d34881be8b84975668eda056049b31ae5c5c1626fabed6a7 AGENTS.md
-6bc17aef39d60d774bf6dc3d234d03bff1e821953e120dee8ac6835b6fdc1863 artifacts/codsemble-0.1.0-rc.sbom.cdx.json
+c5276eba2960f3451bc9df2c9c3da8d6ca6f9319b101cb6a29d74930229251b4 .gitignore
+380658625376043b8a609351d5b0687315b71267f66b4a41c02d47fa8944a746 AGENTS.md
+79f35a945831fbf21125ae19a9badf9d8397942fc9e47818c4e6e165e4f1a986 artifacts/codsemble-0.2.0-plugin.tgz
+81807f0376ad3859d117f1f78dc22e9b7c879741d12adb8c4f04eed7f73c3222 artifacts/codsemble-0.2.0-rc.sbom.cdx.json
406b3247041b21565cc72051099c0fa2e2244fe0cd79e436fce72de547ee8f56 artifacts/runtime-evidence.json
4fc4a92325b0ef2ca5b58c3aa968d0ab462698f001249d4d3974cb838576737a assets/brand/codsemble-github-hero.png
e9fa0ce1862c3da704746c8ccd0d6df504c8eae65a8a61992507b57f2dda2d5e assets/brand/codsemble-github-social-preview.png
ca34cfa4c7ad03edfc6b1dcf764fe44f6cb3465480f7177392ecf2c22e3c979c assets/social/concepts/codsemble-concept-native-compiler.png
-3997a1587097662d82cdbf2310449585daec448d91fcba3f41339a0456fb1525 CHANGELOG.md
+11bb7eb8dc8752d0320f00b10d356c950c00b029394562c13da58a8421fa3b23 CHANGELOG.md
8237734d45581f5ae60b5c0ba597b729846e8a5dcd3d3d50a6535fd0d09f6a5f CODE_OF_CONDUCT.md
e5f38de7b71097b9017788638f7972d9e45e6ea98022131290f4742eaca0e66d CONTRIBUTING.md
-eb5bf40dd1178dd817898802d2b9207e55f51200e61cecdc630d9e3df0826b74 docs/ARCHITECTURE.md
-44b00b27a3d9a28c65a6fe943e998e892ff14818a3624bad27ad93433e3aa84d docs/BRAND.md
-268f623443157ecd9cf0236f358168a67c63001b48944d76bdb3310dc1d6bc4c docs/CONFIG_SAFETY.md
-7808ddd0377e597a4e30307e88eb19e36f77d1cbb00064c25725149baebc9a7a docs/DEFINITION_OF_DONE.md
-554d5be8524983d3715e3f8fb01382c96d21a31c8532a7aa17dfa41664be250d docs/PRIVACY.md
-063e8aebc6f10ebb49b7cb6cc4892590eb309e727e20da4fc2bc88c38800f183 docs/ROLE_CATALOG.md
-114cee8bbc4f32e61c4576119a32185a8681606ffda3cf2021597f2bdde94898 docs/THREAT_MODEL.md
-44676cd4783557174765c28ea584ab42b4f3b45f41ca34ac82742e520a7be23b docs/USAGE.md
-d46734315af1d2fcdd34cae0ccbc7a1927080ed4016ce157aebc0d69e34a6e63 docs/VALIDATION.md
+a60f67dec7941f4602dab7bd402a98ab2d203cb0669a793744b1cc545bf8069c docs/ARCHITECTURE.md
+f929dfc14de476c5ed1ce3c6e27f4cb9d7c9c45f7d3884c9b793174b74a1397c docs/BRAND.md
+01c33100b384c0e39c4ee0338fa4779937762187531d8f1b52f23c11c7c0a757 docs/CONFIG_SAFETY.md
+8e0eb26df0a79f2ac56aee16e3cbe03a497a746d4fe6c2290606df7d42d456b5 docs/DEFINITION_OF_DONE.md
+1c2665454b68bc8d9610b8e36ec1077e459763433798c69dc5e4760ca0539fab docs/MIGRATION_V0_2.md
+7b1b0fae02c9f36405ab7ca0ac97b5bf69e71b23a23e9e7ab8af484170fdd8ee docs/PRIVACY.md
+7e7562598035b96de241449da91487ede44aae85743766f01d18a516e81c9336 docs/PROJECT_CAPABILITY_COMPILER.md
+c915e8f35b51677207069d721e4611058b3072f49658e0350109b55d9a891cde docs/ROLE_CATALOG.md
+48d43adabe2afcb9dd1411e8b6ed6910da628e9b31c46c81d05f2cdb8751a213 docs/THREAT_MODEL.md
+a311c89d7b2593bb1dec4780eb0fefaa7d26746e578b5f145c2079a618e2856d docs/USAGE.md
+05575afa5edbb76589ee5ee00fcc3b1a07cb62a02c2b42fa204115c4e4742540 docs/USEFULNESS_COMPARISON.md
+d54503cc6ed59578600130f6c2605a08662a99688e438f06398a15de07c5b718 docs/VALIDATION.md
2a48ed92f5e11d3b31519cb24e17644f9367015bfb51abad29720a46fb0b5c53 docs/VOICE_APPROVAL.md
41e996c5a736d7d7011bb1eba7cdfaff1ec0379c98bbc9351333824962e63fcd examples/intake.preview.json
06b6a2f5136c646ae2da2cd1125ad656dc176e36177ff032f95778b5d3558889 examples/intake.runtime.json
de1ecec80ccbbcaa886612d89bd7584e4d9640e9d0d4ec0071ed9f665a212f48 GOVERNANCE.md
5c9817c129b98e7bb966bca028c43c19107102ef8e03fe799bffb4354f4ef015 LICENSE
b89d0cb888d7afade19b1694a4a2c2ba44a45038cf3a02565631b54463c1b556 NOTICE
-36cb81ea6e4e21c7d90d79e9e806342fa09ad32e822b97297201c57709d0d20d package-lock.json
-563b3ca671dba10b0dbcbddb4404f668fe57a31486ef84dac48cad6cc5cc1c2b package.json
-5ab43170313732edb527ff5502ce62143e0644087b894f3daf16e9abc715c17d plugins/codsemble/.codex-plugin/plugin.json
-a668f71b534f87bdf585b35dfdd3760f06b7bbe08498fe3af335805b2fa2c61b plugins/codsemble/catalog/generate.mjs
+448a6c48f500cb02d7e06f1759986a20860bd5342a1e2979b3d6cf505f8be14d package-lock.json
+76f23e8bde1ae8b275a148df5fd2c5cc426478427572f1dfac0d7cae24fe714a package.json
+fdf56e8a9fdcda071e8d6ca5e00ba3ad207d369a62d27ed7af39e68fc7f554ad plugins/codsemble/.codex-plugin/plugin.json
+3d5a796d8ee135c6a00d7d98b94582c560b2119f81be5b8da30434b5a3d0df62 plugins/codsemble/catalog/generate.mjs
edd6d03447adb073a2aebd71e0e0b94b5c3a6fa31f4492cd6035623076b672b6 plugins/codsemble/catalog/roles.json
-9fa248ea621bf494c82868a56b7a2d9f57a0f9a99e45a62b613615e45f85f24f plugins/codsemble/scripts/codsemble.mjs
+57767e217d9c94b79126e0afe7b8721d7e901cef1dfea711cc8b7488d7a2cd07 plugins/codsemble/scripts/codsemble.mjs
887c3b5fa744a0214988b5d8422c22f385a626bc19ba4582913b2d25ff621fb3 plugins/codsemble/skills/initialize-team/agents/openai.yaml
-178408317908dae2754526dae79579a7fe33ef7e4e0c8f71ee3b649a158bbdbe plugins/codsemble/skills/initialize-team/SKILL.md
+a8fb77bd16cf480ff98de98832dc1eb6739d832c795aecc2d0de64903fde8e29 plugins/codsemble/skills/initialize-team/SKILL.md
1a476efd980ab66f88580650102255b45b26b8a7c7ea74471a8c985915fd8f8d plugins/codsemble/skills/rollback-team/agents/openai.yaml
96fdc5269e2e2c8f55d3a0c5f4a01421e806b51ef205953d795eeac545e89cdb plugins/codsemble/skills/rollback-team/SKILL.md
ec89a95f29bb67625a44663c08b0939cf522158cd094e31015b83c1a20c2062b plugins/codsemble/skills/team-doctor/agents/openai.yaml
721e7ae8fae15f63dfd9e890fe8b1431c178ad056a7a1f189d76cd7c30b1d420 plugins/codsemble/skills/team-doctor/SKILL.md
a6689d5a15007ba0942ca9ff9538be7a3fc8e1c4b0993191b928063161f57098 plugins/codsemble/skills/update-team/agents/openai.yaml
-f29be74151b0344f63168d304802f7e4f7a0e498dd2960feee81a04dbcc979a1 plugins/codsemble/skills/update-team/SKILL.md
-a97900e92d8102cde373167dc46d1cfb6d62e697b117322be5972448b94d78aa PROJECT_GOAL.md
-9b3fea3f21a878e8dcd6eaf32fbc71bd1d8a2d602bfd835dd86489cf1aeb7c73 README.md
-f1dbedae34f6dcadc5bd10764ceeec842a03a75d33a984af8e5569f9136da291 ROADMAP.md
+51dd29fa9c08aaa6c4fa280c47d5eae0b388d39d72640d0cc8aab84b8d20d363 plugins/codsemble/skills/update-team/SKILL.md
+152eb46211db24f9e4bdb3dd38a11ab0aa7ea6a19bc8a95c0b5d9521fc8ed289 PROJECT_GOAL.md
+74d6b9205727ddf252954cc41440a92dc231b91805e2537d4d8dd764db6ad1c0 README.md
+0504448900848d23d063b412d816ad2f5c5e786b4e6e0cb8754803f8f588cc2d ROADMAP.md
d2d0dd1a73d5d788475bc040d23b617f7d0ee324d8b40bd081a68b27eada18d2 scripts/build.mjs
c6581520fed9f775f87ef1e054c46e786129f03d79b7ad31a441c59e4bdd343a scripts/checksums.mjs
+fc6bb0a52e4839ac4853cca12cf9ba6a7a824b520716b783bf507ea2ad1d66d1 scripts/plugin-archive.mjs
752d2c716c142152abd6a15e3d8959f1ef1543590f3a969b86e4aff8b34a5eb4 scripts/plugin-payload-digest.mjs
-a9f3dc1afb6de40c11c39ea76b6ddd80d8c9a20b4369a8033eee406b15fd011b scripts/sbom.mjs
-476df810b49855b341a13de23644700879962119d80dbb15b974f4cf42d95b61 scripts/validate-catalog.mjs
+a034f12cabe190a207a1d360dfe0563047a3dd3adefdfbb35e4886dfcf0ff8c9 scripts/sbom.mjs
+f210300ed0af67e0b8e76f89f46ed8993aeb597381c3b6ec67d62c254679d3d8 scripts/validate-catalog.mjs
ce3cc1e66b2c89a5103aa3360ef04a60b0c983f84b7221ef3174cb8778f9795d SECURITY.md
-03acaff079500461df8d493d1614ac530fc5fc67e8c15b80a6beb174061e7c62 src/audit.ts
+481630c59ee8bd55c61fe96436d63a5c03c825a9e6407670f7c7fec0bed25428 src/audit.ts
4330112db7e3a02439292a6fae4cf94d95d52369fe54e07107391274135034c9 src/capabilities.ts
+0dc6fee86438a8d82f8d9e8b4b6402832e7ab00019c68b29951d9b05d8bcddbe src/capability-compiler.ts
9961f538a6de4dfd06a6575bf1c626924bce62f86ac0573f4eb288aa8e8652b1 src/catalog.ts
-f65166f09c75deec11f0ab70aa92a751ac043e202fcf450a88965fa72505252f src/cli.ts
-0f3d13e593cb39947a71ba9f4ef5116b59e27a626200a9a3f185095d8b908d24 src/compiler.ts
-3327d991ca0a072abf459e981776ae7f12fb3b66299f20c21b12f1453d9daace src/config.ts
-1edeef8a01805a166f1198971d966c99ef51d232e6667e7b9e8e8a69bf748721 src/confirmation.ts
+b0609e34792cf374265177b892f8518f22153a5329024238719eedd3411fbb0f src/cli.ts
+2622730ce75f119a4807e016573910f6757e823fcfb9c4717466503210eed58a src/compiler.ts
+6117cef1732f3f48410bdd299c91efaf61d1c71888c377dd13570f8cd44af2f4 src/config.ts
+97b81867b1d3d530ee0a1390c68bbc10420c3335a0fa6af63034fa77f55ab5ca src/confirmation.ts
f858f1cc207292cd0230171a6bdc3420271009c2377e88343eb03dd0a9ebdc5e src/doctor.ts
-c1cf10fc73088456627a01ad7e23e6760910be3014e9189db23483e0d594fd20 src/recommend.ts
-9498a7f07b699530ddf40714231801e6f3b05ee91a7feb31a7e388ade5585400 src/schemas.ts
-0d4af3dcbc9bb400f07d8a1871641e022750f289b2711398eee5f03f95318efa src/transaction.ts
-6773de42e9efcf598dd2661cd571af0126be5535be1c786f543344695cd9d8a8 src/types.ts
+85537c54c0738d91c0ce1996f35c4efe0b168d69a7f34406522b6e59e522f73b src/recommend.ts
+cd59986b12077776ed1b7184de2e2c29d0834469f41390c38a2b298c812c87be src/schemas.ts
+87b81cff93bb81441040574fbd28a9b48e9206bef08b5d129aed08cc5c9836cf src/transaction.ts
+e7da832d213b1dc45ad308bc33e6755f3241e39060d30ff6bc9fe0f377c271d7 src/types.ts
1cf68b2b4cec5b49cfa9e7ab97aeb0ad5cb1c4484793df96579475e4967b0bcf src/util.ts
718e08fc0bf75b7d81c7162000bebaec428f1832519a3aeb10e891c76215fd8e SUPPORT.md
-9f8a49ebcf6c519d3aa6aab3b8354381fa46b44a17647035583d9d1dd15ea507 tests/audit.test.ts
+a64ada8ed5b3d6891988e78b6467e4ecfd7e93f0493b5993f96de9fcf97d89d8 tests/audit.test.ts
34fdf298b89d17f94be38f6a43f00c7d92d1e0a7f4617f95134d7c2834b0ef02 tests/capabilities.test.ts
-eb08ca80b5984c3a69e7506548e2680c405386b3f320c8cc0a1e69b59ec8f3b3 tests/catalog.test.ts
-1ced964e4e034f0317f2b3b6503aa5ca990dd54cfc5fb0f9951040a2e8384ffb tests/cli.test.ts
+d2f5e0aa7ffe4e2af38948e76c0e32a0cf0d6ba28939aa345a6e14153f8bc6a3 tests/capability-compiler.test.ts
+536db87b22f1ecf4977871da642eb65629b2b426891b878f96105f0aeef9cac1 tests/catalog.test.ts
+e673ecccf7aa13622db34a9c6c5d33ef010bd9771edbcc7c779ddf3c73b40888 tests/cli.test.ts
f9e6d118c9cd4b52b060b0bcf6aa0fc53cce1b0c9398e51667600e393defbdbf tests/compiler.test.ts
-69d4b23d9744ab8c46c54d184a705a2969b3f293151557b0507cfc467250348b tests/config.test.ts
+e3fa4b1c86d3eed9448dd35cf2b0c8fffc62f0b8d1b9f33e729fb0992d471034 tests/config.test.ts
4ba49ef01a94551fdef79788faa3b6f3bd9f03178d72eabcece788bfa102b517 tests/confirmation.test.ts
6a5a6bfc3fc981a335107bb929388d1fc392b661eaf1d5a7eead9796776a2a85 tests/doctor.test.ts
c34cbe8eef0a1024cdebaf2df04a7d42da0f6e75a05a77e3fffaa028d5b61697 tests/fixtures/audit/typescript-app/.codex/agents/reviewer.toml
@@ -92,10 +99,19 @@ e4090a069325b507a404ae31dd5f443f2ff08acb93aef49e1952ad80a421f419 tests/fixtures
a2098bd92b10bf8b816d24b7556b1ce8c49a879d130489065ef1051c17e042f6 tests/fixtures/audit/typescript-app/src/index.ts
8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881 tests/fixtures/audit/typescript-app/tests/index.test.ts
fc9fc38c21441b7f67a91280ed28b8ca4ad67fc69d713db441f5c0fd9a6abf9f tests/fixtures/audit/typescript-app/tsconfig.json
-6ead80bdb340d1d22d9234df6e19fbbf72376560185ca763ccb0054d305ece91 tests/golden.test.ts
-4f45a3672198f040865e1805b28711bbf9721e3b08cf431a6bb76c6a9814aebb tests/property.test.ts
-0c26225c4a575960fdc79f10cc6ab90a916565337e98c8b5a021e4146ce44de4 tests/recommend.test.ts
+8a98cd1594a7a746cf3839d8fd69b507043520d361011dae679bb3caa44b6212 tests/fixtures/lifecycle/interrupted.apply.pending.json
+8788a4fcea4745b900da6393eb03b792176464b20ec00b25177e99562d508cb7 tests/fixtures/pcc/polyglot-monorepo.golden.json
+93e2fb69e4d3dc9341f086f01bd5132a4692d4822b9e669ce518c74c06c8d2c7 tests/fixtures/pcc/projects.json
+0d17a432049e05f6d91977ae302541fb43162e3809ac90efe763569ac1adc0aa tests/fixtures/pcc/regulated-delivery.golden.json
+87b38cf33600461fb67f3b54f04078ec34b46facb58f269bd3ac8325ba89ac6a tests/golden.test.ts
+0885b9c561e752f7eede028e0e52ea0845c976ae6f4289c7d0dd4210fafe8cd6 tests/migration.test.ts
+ec650cce26a4ff7227cbca3887977fdf5cbe074b0317ffaa89348d3b4ac89677 tests/pcc-fixtures.test.ts
+77cd8c2a22bb8f72fde677a80eec79f7515ac1a98ce26ad66b0065c5d3a7dacd tests/pcc-golden.test.ts
+cb3599dbb880fb4df27ba57766a5ceeb7c2c5e7fdabca0e761563714ed5eb8bf tests/pcc-property.test.ts
+a4f438ce88848fe11b592d2a18edb729879fdbc2c76bc6cb1aabb9979bd04586 tests/property.test.ts
+9e3007bbc59412086b644aae2823bae8a5fd722c3f368429ca6e95cb44998095 tests/recommend.test.ts
f1b82b3a6b38f1020a85301ea224bff8cd746d8099ce83dcda9aa3cb0c23f763 tests/security.test.ts
-91ccb8de77d8a21ad459d7e5f8050686eba166f85908453382eb504d3dea0f71 tests/transaction.test.ts
+2446abcfc476e3c9e2c77d74d32acc6c24e0b9bd13fabfdff613e0189d5546aa tests/transaction.test.ts
+5634f214e733ade2ab10984675999ec709736c6894bbc878cac11f20fc53a7cc tests/usefulness.test.ts
8b0b95b8cdb36539d13f62bddf2a1b241e6dc99abd853de0048e728a8f6ec935 tsconfig.json
598beb934e2737385cfb7a1b49965e099564ea3672cb65b8d4ac59839354fc29 vitest.config.ts
diff --git a/artifacts/codsemble-0.2.0-plugin.tgz b/artifacts/codsemble-0.2.0-plugin.tgz
new file mode 100644
index 0000000000000000000000000000000000000000..f4333bba477f65ff15d19bc5b4edf14cda678aa0
GIT binary patch
literal 166298
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