Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

All notable changes to @rpamis/comet will be documented in this file.

## What's Changed [0.4.0-rc.2] - 2026-09-01

### Fixed

- **Native verification loop-stop handoff**: When Native verification paused after repeated failures or repeated no-progress results, the Agent received no user-facing message at the pause point, so sessions could keep re-checking the same candidate instead of asking the user whether to continue repairing or adjust the requirements. The Runtime now returns an explicit user decision request with ready-to-relay bilingual messages for the loop stop, and the same applies when a Verifier blocker waits on information only the user can provide.

## What's Changed [0.4.0-rc.1] - 2026-08-31

### Added
Expand Down
2 changes: 1 addition & 1 deletion assets/manifest.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "0.4.0-rc.1",
"version": "0.4.0-rc.2",
"skills": [
"comet/SKILL.md",
"comet/agents/openai.yaml",
Expand Down

Large diffs are not rendered by default.

94 changes: 47 additions & 47 deletions assets/skills/comet-native/scripts/comet-native-doctor.mjs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion assets/skills/comet-native/scripts/comet-native-new.mjs

Large diffs are not rendered by default.

64 changes: 32 additions & 32 deletions assets/skills/comet-native/scripts/comet-native-next.mjs

Large diffs are not rendered by default.

162 changes: 81 additions & 81 deletions assets/skills/comet-native/scripts/comet-native-runtime.mjs

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions assets/skills/comet-native/scripts/comet-native-select.mjs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion assets/skills/comet-native/scripts/comet-native-show.mjs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion assets/skills/comet-native/scripts/comet-native-spec.mjs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion assets/skills/comet-native/scripts/comet-native-status.mjs

Large diffs are not rendered by default.

20 changes: 18 additions & 2 deletions domains/comet-native/native-loop-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -654,7 +654,10 @@ export function confirmNativeVerifierUnavailable(options: {
});
}

export function resolveNativeVerifierBlocker(stateInput: NativePortableState): NativePortableState {
export function resolveNativeVerifierBlocker(
stateInput: NativePortableState,
options?: { reason?: string; now?: Date },
): NativePortableState {
const state = parseNativePortableState(stateInput);
if (
state.phase !== 'verify' ||
Expand All @@ -670,8 +673,21 @@ export function resolveNativeVerifierBlocker(stateInput: NativePortableState): N
) {
throw new Error('Native change is not awaiting resolution of a semantic Verifier blocker');
}
// The user's resolution context is the only place the next Verifier round
// can learn what information unblocked the semantic judgment, so it must be
// persisted in history instead of dying with the command invocation.
const withHistory = appendNativePortableHistory(
state,
historyEntry({
state,
outcome: 'recovery',
summary:
options?.reason ?? 'Resolved the semantic Verifier blocker and resumed verification.',
Comment on lines +684 to +690

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Blocker answer remains paginated

When a Native change has at least 31 acceptance and spec detail items, this stores the user's blocker answer only in history after those items, while the replacement Verifier receives an unpaged details command and is instructed to read only pages covering scopeIds. The answer is therefore omitted from the Verifier's context, causing it to block again on the same missing information.

Knowledge Base Used: Native workflow runtime

Comment on lines +689 to +690

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Blank recovery context is dispatched

When an Agent resolves the blocker with a whitespace-only --summary, the CLI accepts the value and this branch persists it unchanged. latestRecoveryContext then sends that blank text to the replacement Verifier, causing it to lack the information needed for a verdict and return to the same blocker prompt.

Knowledge Base Used: Native workflow runtime

completedAt: (options?.now ?? new Date()).toISOString(),
}),
);
Comment on lines +684 to +693

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Blocker 回答未传递

当用户通过 --resolve-verifier-blocker --summary 补充缺失信息时,这段代码只把回答写入分页的 portable history,而新的 verifierDispatch 不携带该回答,且替代 Verifier 只被要求读取覆盖 scopeIds 的详情页。当 recovery history 不在这些页面中时,替代 Verifier 无法获得用户的回答,并会因相同信息缺失再次返回 blocked。

Knowledge Base Used: Native workflow runtime

Comment thread
greptile-apps[bot] marked this conversation as resolved.
return parseNativePortableState({
...state,
...withHistory,
status: 'active',
state_version: nextVersion(state),
verification_result: 'pending',
Expand Down
1 change: 1 addition & 0 deletions domains/comet-native/native-next-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,7 @@ export async function nativeNextCommand(
state = await resolveNativePortableVerifierBlocker({
paths: configured.paths,
name,
reason: summary,
expectedContinuation,
});
} else {
Expand Down
56 changes: 56 additions & 0 deletions domains/comet-native/native-portable-continuation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,62 @@ function nativePortableUserCommunication(
};
}

if (
state.phase === 'verify' &&
state.status === 'await-user' &&
state.loop.next_action === 'resolve-verifier-blocker'
) {
return state.language === 'zh-CN'
? {
required: true,
message:
'验证暂时无法下结论,因为缺少只有你能提供的信息,例如外部系统的真实行为或某个业务决定。你的代码和已经完成的检查都已安全保留。补充所需信息后可继续验证,也可以选择修改实现或调整需求。',
suggestedReply: null,
agentInstruction:
'向用户转述 message,请用户补充缺失的信息,或在继续验证(resolve-verifier-blocker)、修改实现、调整需求之间明确选择,再执行 commandAlternatives 中对应的完整命令。不要把“继续”当作默认选择,也不要展示内部轮次、计数、路径或恢复步骤。',
Comment thread
sourcery-ai[bot] marked this conversation as resolved.
}
: {
required: true,
message:
'Verification cannot reach a verdict yet because information only you can provide is missing, such as the real behavior of an external system or a business decision. Your code and completed checks are safely preserved. Supply the missing information to resume verification, or choose to revise the implementation or the requirements.',
suggestedReply: null,
agentInstruction:
'Relay message and ask the user to supply the missing information or explicitly choose between resuming verification (resolve-verifier-blocker), revising the implementation, and revising the requirements; run the matching commandAlternative afterwards. Do not treat “Continue” as a default choice, and do not expose internal rounds, counters, paths, or recovery steps.',
Comment on lines +195 to +198

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Blocker response is discarded

When a Verifier needs information from the user, this handoff promises that supplying it will resume verification, but the required --summary is not forwarded or persisted before the replacement Verifier is dispatched. The replacement receives the same pending scope without the user's resolution, causing it to block on the same missing information and return the session to this prompt.

Knowledge Base Used: Native workflow runtime

};
}

if (
state.phase === 'verify' &&
state.status === 'await-user' &&
state.loop.next_action === 'await-user'
) {
// The stop path is not persisted separately: no_progress_count >= 3 means
// the three-non-progressive-results stop, otherwise the failed-iteration
// budget was exhausted even if every round made progress.
const stalled = state.loop.no_progress_count >= 3;
const zhMessage = stalled
? '验证已连续三轮失败且未通过的验收场景一直没有减少,本次修改已暂停,以避免在同一个问题上反复循环。你的代码和已经完成的检查都已安全保留。可以让 Builder 换一种修复思路继续,也可以回到需求阶段调整验收项。'
: '本次修改的验证失败次数已用完配置的预算,因此暂停等待你的决定,而不是自动重试。你的代码和已经完成的检查都已安全保留。可以让 Builder 换一种修复思路继续,也可以回到需求阶段调整验收项。';
const enMessage = stalled
? 'Verification has failed three times in a row without the unresolved scenarios shrinking, so this change is paused to avoid looping on the same problem. Your code and completed checks are safely preserved. You can have the Builder try a different repair approach, or go back and adjust the requirements.'
: 'Verification for this change has used its configured failure budget, so it is paused for your decision instead of retrying automatically. Your code and completed checks are safely preserved. You can have the Builder continue with a different repair approach, or go back and adjust the requirements.';
Comment thread
greptile-apps[bot] marked this conversation as resolved.
Outdated
return state.language === 'zh-CN'
? {
required: true,
Comment thread
greptile-apps[bot] marked this conversation as resolved.
Outdated
message: zhMessage,
suggestedReply: '继续修复',
agentInstruction:
'向用户转述 message 和 suggestedReply,等待用户在“继续修复实现”(revise-implementation)与“调整需求”(revise-requirements)之间明确选择,再执行 commandAlternatives 中对应的完整命令。选择继续修复时,要求 Builder 更换修复思路。不要替用户选择,也不要展示内部轮次、计数、路径或恢复步骤。',
}
: {
required: true,
message: enMessage,
suggestedReply: 'Continue repairing',
agentInstruction:
'Relay message and suggestedReply, then wait for the user to explicitly choose between continuing the implementation (revise-implementation) and adjusting the requirements (revise-requirements); run the matching commandAlternative afterwards. When continuing, ask the Builder to change its repair approach. Do not choose for the user, and do not expose internal rounds, counters, paths, or recovery steps.',
};
}

return noUserUpdate(
localized(
state,
Expand Down
3 changes: 2 additions & 1 deletion domains/comet-native/native-portable-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2008,6 +2008,7 @@ export async function confirmNativePortableVerifierUnavailable(options: {
export async function resolveNativePortableVerifierBlocker(options: {
paths: NativeProjectPaths;
name: string;
reason?: string;
expectedContinuation?: NativePortableExpectedContinuation;
}): Promise<NativePortableState> {
return withNativeMutationLock(
Expand All @@ -2022,7 +2023,7 @@ export async function resolveNativePortableVerifierBlocker(options: {
});
await ensureNativePortableAcceptanceCurrentLocked({ paths: options.paths, state });
const local = await readCurrentLocalExecution({ paths: options.paths, state });
const next = resolveNativeVerifierBlocker(state);
const next = resolveNativeVerifierBlocker(state, { reason: options.reason });
const written = await writePortableMutation({ paths: options.paths, previous: state, next });
await writeNativeLocalExecution(
nativeLocalExecutionFile(options.paths, state.name),
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@rpamis/comet",
"version": "0.4.0-rc.1",
"version": "0.4.0-rc.2",
"description": "Agent Skill Harness For Turning Ideas Into Evaluated Workflows",
"keywords": [
"comet",
Expand Down
2 changes: 1 addition & 1 deletion test/app/cli-help.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ describe('CLI help text', () => {
expect(help.status, help.stderr).toBe(0);
expect(help.stdout).toContain(tagline);
expect(packageJson.description).toBe(tagline);
expect(packageJson.version).toBe('0.4.0-rc.1');
expect(packageJson.version).toBe('0.4.0-rc.2');
});

it('marks bundle as the advanced backend and skill Engine runs as advanced', () => {
Expand Down
61 changes: 61 additions & 0 deletions test/domains/comet-native/native-loop-runtime.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
confirmNativePortableAcceptance,
recordNativeVerifierExecutionError,
reserveNativeVerifierAttempt,
resolveNativeVerifierBlocker,
returnNativeCandidateToBuild,
retryNativeVerifier,
submitNativeBuilderCandidate,
Expand Down Expand Up @@ -639,6 +640,12 @@ describe('Native portable Build/Verify loop', () => {
action: 'resolve-loop-stop',
commandArgs: null,
requiredInputs: ['summary', 'user-decision'],
userCommunication: {
required: true,
message: expect.stringContaining('paused to avoid looping on the same problem'),
suggestedReply: 'Continue repairing',
agentInstruction: expect.stringContaining('revise-implementation'),
},
commandAlternatives: expect.arrayContaining([
expect.objectContaining({
name: 'revise-implementation',
Expand All @@ -652,6 +659,40 @@ describe('Native portable Build/Verify loop', () => {
}),
]),
});
expect(
nativePortableContinuation({ ...state, language: 'zh-CN' }).userCommunication,
).toMatchObject({
required: true,
message: expect.stringContaining('本次修改已暂停'),
suggestedReply: '继续修复',
agentInstruction: expect.stringContaining('revise-implementation'),
});
});

it('explains a budget-exhausted stop without calling it a no-progress loop', () => {
const { state, runner } = buildState();
const stopped = applyNativeVerifierEnvelope({
state,
envelope: envelope(runner, state, 'fail', ['A1']),
checks,
maxVerifyFailures: 1,
}).state;

expect(stopped).toMatchObject({
status: 'await-user',
loop: { failed_iteration_count: 1, no_progress_count: 0, stage: 'await-user' },
});
expect(nativePortableContinuation(stopped).userCommunication).toMatchObject({
required: true,
message: expect.stringContaining('used its configured failure budget'),
suggestedReply: 'Continue repairing',
});
expect(
nativePortableContinuation({ ...stopped, language: 'zh-CN' }).userCommunication,
).toMatchObject({
required: true,
message: expect.stringContaining('已用完配置的预算'),
});
});

it('keeps a report reference for a semantic blocker', () => {
Expand All @@ -669,6 +710,26 @@ describe('Native portable Build/Verify loop', () => {
verification_report: 'verification.md',
loop: { next_action: 'resolve-verifier-blocker' },
});
expect(nativePortableContinuation(result).userCommunication).toMatchObject({
required: true,
message: expect.stringContaining('information only you can provide'),
suggestedReply: null,
agentInstruction: expect.stringContaining('resolve-verifier-blocker'),
});
expect(
nativePortableContinuation({ ...result, language: 'zh-CN' }).userCommunication,
).toMatchObject({
required: true,
message: expect.stringContaining('缺少只有你能提供的信息'),
});

const resumed = resolveNativeVerifierBlocker(result, {
reason: 'The external service returns 429 under load; that behavior is expected.',
});
expect(resumed.history[resumed.history.length - 1]).toMatchObject({
outcome: 'recovery',
summary: { text: 'The external service returns 429 under load; that behavior is expected.' },
});
});

it('blocks after three execution errors without consuming semantic failure budgets', () => {
Expand Down
2 changes: 1 addition & 1 deletion test/repository/release-metadata.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ describe('release metadata', () => {
readFileSync(path.join(repositoryRoot, 'assets', 'manifest.json'), 'utf8'),
) as { version: string };

expect(packageJson.version).toBe('0.4.0-rc.1');
expect(packageJson.version).toBe('0.4.0-rc.2');
expect(packageLock.version).toBe(packageJson.version);
expect(packageLock.packages[''].version).toBe(packageJson.version);
expect(assetsManifest.version).toBe(packageJson.version);
Expand Down
Loading