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
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,22 @@

All notable changes to Doable Agent Plugins are documented here.

## [0.2.3] - 2026-08-21

### Changed

- Keep one post-create context connection open across sequential TRD follow-up
Rounds. The original copied DQ code resolves to the newest published Round
until the user stops the coding-agent task.
- Fetch the current TRD for each newly resolved follow-up Round while keeping
code, tests, and runtime evidence descriptive rather than treating it as
authoritative product intent.

### Fixed

- Preserve an existing workspace client reference when a new Round has not yet
been bound, avoiding duplicate workspace profiles and unnecessary approval.

## [0.2.2] - 2026-08-20

### Changed
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.2` | Resolve context requests or start a managed feature-testing workflow | Configured Doable MCP |
| `doable-code-context` | `0.2.3` | Resolve context requests or start a managed feature-testing workflow | Configured 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.2",
"version": "0.2.3",
"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.2",
"version": "0.2.3",
"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.2",
"version": "0.2.3",
"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.2",
"version": "0.2.3",
"description": "Connect private code to Doable through MCP, resolve grounded context requests, and start managed feature-testing workflows.",
"author": {
"name": "Doable AI"
Expand Down
52 changes: 45 additions & 7 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.2" });
const CLIENT = Object.freeze({ name: "doable-code-context", version: "0.2.3" });
const STATE_SCHEMA_VERSION = "1";
const SUBMISSION_SCHEMA_VERSION = "1";

Expand Down Expand Up @@ -324,7 +324,7 @@ function normalizeHandshake(data, localWorkspaceId) {
"handshake client workspace id",
{ max: 160 },
)
: localWorkspaceId;
: null;
const workspaceProfileRevision = workspace?.profile_revision ?? workspace?.profileRevision ?? 0;
assert(
Number.isInteger(Number(workspaceProfileRevision)) && Number(workspaceProfileRevision) >= 0,
Expand Down Expand Up @@ -737,8 +737,10 @@ function recordWorkspaceSync(options) {
console.log(`Product surfaces: ${unique(state.repositories.flatMap((repository) => repository.surfaces), "product surfaces").sort().join(", ")}`);
}

function watchAction(status, openQuestions) {
if (["creating", "consumed", "cancelled"].includes(status)) return "stop";
function watchAction(roundUse, status, openQuestions) {
if (["creating", "consumed", "cancelled"].includes(status)) {
return roundUse === "follow_up" ? "wait" : "stop";
}
if (status === "open_for_agent" && openQuestions.length > 0) return "answer";
return "wait";
}
Expand Down Expand Up @@ -835,10 +837,34 @@ function normalizeRound(data, state, requestedCode) {
const round = data.round || data;
const id = string(round.round_id || round.id, "round id", { max: 160 });
const code = string(round.round_code || round.code, "round code", { max: 64 });
assert(code.toLowerCase() === requestedCode.toLowerCase(), "Doable returned a different round code");
const connectionCode = string(
round.connection_round_code || round.connectionRoundCode || code,
"connection round code",
{ max: 64 },
);
assert(
connectionCode.toLowerCase() === requestedCode.toLowerCase(),
"Doable returned a different context connection",
);
const workspaceId = round.workspace_id || round.workspaceId || "";
const revision = Number(round.revision);
assert(Number.isInteger(revision) && revision > 0, "round revision must be a positive integer");
const roundUse = round.round_use || round.roundUse || "pre_create";
assert(
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",
);
const rawTestSuitePublicId = round.test_suite_public_id || round.testSuitePublicId || "";
const testSuitePublicId = rawTestSuitePublicId
? string(rawTestSuitePublicId, "test suite public id", { max: 160 })
: null;
if (roundUse === "follow_up") {
assert(testSuitePublicId, "follow-up round is missing its test suite public id");
}
const status = round.status || "open_for_agent";
assert(
["open_for_agent", "needs_attention", "ready_to_create", "creating", "consumed", "cancelled"].includes(status),
Expand Down Expand Up @@ -893,14 +919,23 @@ function normalizeRound(data, state, requestedCode) {
const baseCount = [...questions, ...establishedContext].filter(
(question) => question.purpose === "base_context",
).length;
assert(baseCount === 1, "round must contain exactly one base feature context request");
if (roundUse === "pre_create") {
assert(baseCount === 1, "pre-create round must contain exactly one base feature context request");
} else {
// New follow-up rounds contain only supplements. Accept one legacy base
// item so an already-published round can still reach a terminal state.
assert(baseCount <= 1, "follow-up round contains multiple base feature context requests");
}
}
const action = watchAction(status, questions);
const action = watchAction(roundUse, status, questions);
return {
id,
code,
connectionCode,
workspaceId: workspaceId || null,
revision,
roundUse,
testSuitePublicId,
status,
action,
featureScope,
Expand Down Expand Up @@ -967,6 +1002,9 @@ function recordRound(options) {
});
}
console.log(`Round: ${round.code} revision ${round.revision}`);
if (round.connectionCode !== round.code) {
console.log(`Connection: ${round.connectionCode}`);
}
console.log(`Status: ${round.status}`);
console.log(`Next action: ${round.action}`);
console.log(`Scope: ${round.featureScope}`);
Expand Down
Loading
Loading