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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@

All notable changes to Doable Agent Plugins are documented here.

## [0.2.8] - 2026-09-25

### Fixed

- Accept a server-bound pre-create successor when a TRD needs more context to
finish creation. Keep checking the original connection code and local workspace.
- Requires the matching TRD connection resolver fix; older servers retain their
existing behavior. Cross-repository contract: `getdoable/trd`
`docs/change-sets/pre-create-code-context-toggle.yaml`.

## [0.2.7] - 2026-09-17

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Official agent plugins for [Doable](https://getdoable.ai), supporting Codex, Cla

| Plugin | Version | Purpose | Network |
| --- | --- | --- | --- |
| `doable-code-context` | `0.2.7` | Resolve context requests or start a managed feature-testing workflow | Doable MCP |
| `doable-code-context` | `0.2.8` | Resolve context requests or start a managed feature-testing workflow | Doable MCP |

## Workflow

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "doable-agent-plugins",
"version": "0.2.7",
"version": "0.2.8",
"private": true,
"description": "Official installable agent plugins for Doable.",
"license": "MIT",
Expand Down
2 changes: 1 addition & 1 deletion plugins/doable-code-context/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "doable-code-context",
"version": "0.2.7",
"version": "0.2.8",
"description": "Connect private code to Doable through MCP, resolve grounded context requests, and start managed feature-testing workflows.",
"author": {
"name": "Doable AI",
Expand Down
2 changes: 1 addition & 1 deletion plugins/doable-code-context/.codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "doable-code-context",
"version": "0.2.7",
"version": "0.2.8",
"description": "Connect private code to Doable through MCP, resolve grounded context requests, and start managed feature-testing workflows.",
"author": {
"name": "Doable AI",
Expand Down
2 changes: 1 addition & 1 deletion plugins/doable-code-context/.cursor-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "doable-code-context",
"displayName": "Doable Code Context",
"version": "0.2.7",
"version": "0.2.8",
"description": "Connect private code to Doable through MCP, resolve grounded context requests, and start managed feature-testing workflows.",
"author": {
"name": "Doable AI"
Expand Down
9 changes: 4 additions & 5 deletions plugins/doable-code-context/scripts/doable-code-context.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
import { basename, dirname, isAbsolute, join, resolve, sep } from "node:path";
import { execFileSync } from "node:child_process";

const CLIENT = Object.freeze({ name: "doable-code-context", version: "0.2.7" });
const CLIENT = Object.freeze({ name: "doable-code-context", version: "0.2.8" });
const STATE_SCHEMA_VERSION = "1";
const SUBMISSION_SCHEMA_VERSION = "1";

Expand Down Expand Up @@ -854,10 +854,9 @@ function normalizeRound(data, state, requestedCode) {
roundUse === "pre_create" || roundUse === "follow_up",
"round use must be pre_create or follow_up",
);
assert(
code.toLowerCase() === requestedCode.toLowerCase() || roundUse === "follow_up",
"Doable returned a different pre-create round code",
);
// The server may resolve the connection to a create-resume pre-create Round.
// The original connection code was checked above; workspace checks below still
// reject a successor from another local workspace.
const rawTestSuitePublicId = round.test_suite_public_id || round.testSuitePublicId || "";
const testSuitePublicId = rawTestSuitePublicId
? string(rawTestSuitePublicId, "test suite public id", { max: 160 })
Expand Down
2 changes: 1 addition & 1 deletion scripts/verify-release.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const semver = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z.-]+)?(?:
const plugins = [
{
name: "doable-code-context",
version: "0.2.7",
version: "0.2.8",
skillNames: ["doable-connect", "doable-answer-questions", "doable-test-feature"],
network: "bundled-doable-mcp-config",
},
Expand Down
34 changes: 34 additions & 0 deletions tests/doable-code-context-helper.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -945,3 +945,37 @@ test("agent-origin helper records the exact MCP round and finalize result", asyn
), /Next action: wait/);

});

test("a pre-create successor requires the original connection and matching workspace", async (t) => {
const testRoot = mkdtempSync(join(tmpdir(), "doable-resume-connection-"));
t.after(() => rmSync(testRoot, { recursive: true, force: true }));
mkdirSync(join(testRoot, ".doable"));
const statePath = join(testRoot, ".doable", "workspace-private.json");
writeFileSync(statePath, JSON.stringify({
schemaVersion: "1", workspace: { localId: "local-fixture", serverId: "workspace-safe" },
organization: { id: "org-safe" }, repositories: [],
privateFingerprintKey: "local-test-only",
sync: { profileFingerprint: "fixture" }, profile: { profileFingerprint: "fixture" },
}));
const responsePath = join(testRoot, "resume-round.json");
const round = {
round_id: "resume-round", round_code: "DQ-RESUME1", connection_round_code: "DQ-ORIGIN1",
round_use: "pre_create", workspace_id: "workspace-safe", status: "open_for_agent",
next_action: "answer", revision: 1, feature_scope: "Account recovery",
questions: [{ id: "base-question", purpose: "base_context", question: "Ground account recovery.", why: "Resume creation." }],
};
const environment = { TEST_WORKSPACE: testRoot };
const args = ["record-round", "--code", "DQ-ORIGIN1", "--response", responsePath, "--state", statePath];
writeFileSync(responsePath, JSON.stringify(round));
assert.match(await runHelper(args, environment), /Next action: answer/);
const candidate = JSON.parse(readFileSync(join(testRoot, ".doable", "requests", "DQ-RESUME1", "submission-r1.json"), "utf8"));
assert.equal(candidate.round.id, "resume-round");
assert.deepEqual(candidate.answers.map(answer => answer.questionId), ["base-question"]);

for (const changes of [{ connection_round_code: undefined }, { connection_round_code: "DQ-OTHER1" }]) {
writeFileSync(responsePath, JSON.stringify({ ...round, ...changes }));
await assert.rejects(runHelper(args, environment), /different context connection/);
}
writeFileSync(responsePath, JSON.stringify({ ...round, workspace_id: "workspace-other" }));
await assert.rejects(runHelper(args, environment), /different workspace/);
});
Loading