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
5 changes: 2 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,12 @@

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

## What's Changed [0.4.0-beta.20] - 2026-08-16
## What's Changed [0.4.0-beta.19] - 2026-08-15

### Added

- **Repository-owned Native pull-request finish providers**: Projects can opt into a structured repository command for PR title, body, template, and policy validation while Comet retains commit, push, remote base/head/SHA verification, existing-PR reuse, recoverable failure state, and safe worktree cleanup.

## What's Changed [0.4.0-beta.19] - 2026-08-15

### Changed

- **Dashboard artifact previews**: Fullscreen previews now close with Escape, keep long tables horizontally scrollable, preserve readable table headers, and use a larger directory navigation scale.
Expand All @@ -18,6 +16,7 @@ All notable changes to @rpamis/comet will be documented in this file.

### Fixed

- **Classic Ambient Resume**: `comet init` and `comet update` now keep the managed Ambient Resume instructions for Classic-only projects when `ambient_resume` is enabled, so re-running the commands no longer removes the block from `AGENTS.md` or `CLAUDE.md`.
- **Fork pull request greetings**: First-time contributors now receive the repository guidance comment when opening a pull request from a fork, without weakening the read-only permissions of workflows that execute contributor code.
- **Pull request template checks**: Pull requests now receive an actionable comment and a failing check when items from the repository template are missing or its checklist is incomplete.

Expand Down
3 changes: 1 addition & 2 deletions app/commands/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1124,8 +1124,7 @@ export async function initCommand(
await syncCometProjectInstructions(
projectPath,
language.id,
includesWorkflow(workflowSelection, 'native') &&
(initialProjectConfigDocument?.ambient_resume ?? true),
initialProjectConfigDocument?.ambient_resume ?? true,
);

const successfulCometPlatforms = new Set(
Expand Down
2 changes: 1 addition & 1 deletion app/commands/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1975,7 +1975,7 @@ async function updateSingleProject(
const projectInstructionResult = await syncCometProjectInstructions(
projectPath,
projectLanguageId,
nativeProject && (projectConfigDocument?.ambient_resume ?? true),
projectConfigDocument?.ambient_resume ?? true,
);
projectInstructionsUpdated = projectInstructionResult.changed;
if (projectInstructionsUpdated > 0) {
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-beta.20",
"version": "0.4.0-beta.19",
"skills": [
"comet/SKILL.md",
"comet/agents/openai.yaml",
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-beta.20",
"version": "0.4.0-beta.19",
"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-beta.20');
expect(packageJson.version).toBe('0.4.0-beta.19');
});

it('marks bundle as the advanced backend and skill Engine runs as advanced', () => {
Expand Down
25 changes: 25 additions & 0 deletions test/app/init-e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,31 @@ describe('comet init E2E', () => {
).rejects.toMatchObject({ code: 'ENOENT' });
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion (testing): Add a negative-path init E2E test for Classic-only projects with ambient_resume: false.

Please also add a negative-path E2E that initializes a Classic-only project with ambient_resume: false (or the probe disabled via config) and asserts that no <comet-ambient-resume> block is written to AGENTS.md/CLAUDE.md and existing user content is preserved. This will mirror the non-Classic removal behavior and verify that ambient_resume: false semantics remain unchanged.

Suggested implementation:

  it('installs Ambient Resume instructions for Classic-only project init', async () => {
    mockExternalSuccess();
    await fs.mkdir(path.join(tmpDir, '.claude'), { recursive: true });
    await fs.writeFile(path.join(tmpDir, 'AGENTS.md'), '# User\n\nKeep this.\n', 'utf8');
    await fs.writeFile(path.join(tmpDir, 'CLAUDE.md'), '# User\n\nAlso keep this.\n', 'utf8');

    const { initCommand } = await import('../../app/commands/init.js');
    const result = await captureJsonOutput(() =>
      initCommand(tmpDir, { yes: true, json: true, workflow: 'classic', language: 'en' }),
    );

    expect(result).toMatchObject({
      workflow: 'classic',
      initializedWorkflows: ['classic'],
    });
    const agents = await fs.readFile(path.join(tmpDir, 'AGENTS.md'), 'utf8');
    const claude = await fs.readFile(path.join(tmpDir, 'CLAUDE.md'), 'utf8');
    for (const content of [agents, claude]) {
      expect(content).toContain('<comet-ambient-resume>');
      expect(content).toContain('comet resume-probe . --stdin --json');
    }
    expect(agents).toContain('# User\n\nKeep this.');
    expect(claude).toContain('# User\n\nAlso keep this.');
  });

  it('does not install Ambient Resume instructions when Classic-only project has ambient_resume disabled', async () => {
    mockExternalSuccess();
    await fs.mkdir(path.join(tmpDir, '.claude'), { recursive: true });
    await fs.writeFile(path.join(tmpDir, 'AGENTS.md'), '# User\n\nKeep this.\n', 'utf8');
    await fs.writeFile(path.join(tmpDir, 'CLAUDE.md'), '# User\n\nAlso keep this.\n', 'utf8');

    // Disable Ambient Resume via config for a Classic-only project
    await fs.writeFile(
      path.join(tmpDir, '.claude', 'config.json'),
      JSON.stringify(
        {
          ambient_resume: false,
        },
        null,
        2,
      ),
      'utf8',
    );

    const { initCommand } = await import('../../app/commands/init.js');
    const result = await captureJsonOutput(() =>
      initCommand(tmpDir, { yes: true, json: true, workflow: 'classic', language: 'en' }),
    );

    expect(result).toMatchObject({
      workflow: 'classic',
      initializedWorkflows: ['classic'],
    });

    const agents = await fs.readFile(path.join(tmpDir, 'AGENTS.md'), 'utf8');
    const claude = await fs.readFile(path.join(tmpDir, 'CLAUDE.md'), 'utf8');

    for (const content of [agents, claude]) {
      expect(content).not.toContain('<comet-ambient-resume>');
      expect(content).not.toContain('comet resume-probe . --stdin --json');
    }

    expect(agents).toContain('# User\n\nKeep this.');
    expect(claude).toContain('# User\n\nAlso keep this.');
  });

  it('adds Classic with the docs layout when a Native-only project is reinitialized as Both', async () => {
    mockExternalSuccess();

If this repository uses a different config file name, location, or schema to disable Ambient Resume (e.g. .claude/project.json, YAML, or a nested features: { ambient_resume: false } object), adjust the fs.writeFile path and JSON structure in the new test to match the existing non-Classic negative-path Ambient Resume tests so that the init command actually observes the disabled setting.


it('installs Ambient Resume instructions for Classic-only project init', async () => {
mockExternalSuccess();
await fs.mkdir(path.join(tmpDir, '.claude'), { recursive: true });
await fs.writeFile(path.join(tmpDir, 'AGENTS.md'), '# User\n\nKeep this.\n', 'utf8');
await fs.writeFile(path.join(tmpDir, 'CLAUDE.md'), '# User\n\nAlso keep this.\n', 'utf8');

const { initCommand } = await import('../../app/commands/init.js');
const result = await captureJsonOutput(() =>
initCommand(tmpDir, { yes: true, json: true, workflow: 'classic', language: 'en' }),
);

expect(result).toMatchObject({
workflow: 'classic',
initializedWorkflows: ['classic'],
});
const agents = await fs.readFile(path.join(tmpDir, 'AGENTS.md'), 'utf8');
const claude = await fs.readFile(path.join(tmpDir, 'CLAUDE.md'), 'utf8');
for (const content of [agents, claude]) {
expect(content).toContain('<comet-ambient-resume>');
expect(content).toContain('comet resume-probe . --stdin --json');
}
expect(agents).toContain('# User\n\nKeep this.');
expect(claude).toContain('# User\n\nAlso keep this.');
});

it('adds Classic with the docs layout when a Native-only project is reinitialized as Both', async () => {
mockExternalSuccess();
await fs.mkdir(path.join(tmpDir, '.claude'), { recursive: true });
Expand Down
30 changes: 30 additions & 0 deletions test/app/update.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3526,6 +3526,36 @@ describe('update command helpers', () => {
expect(claude).toContain('<comet-ambient-resume>');
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion (testing): Add coverage for Classic-only projects with ambient_resume: false in the update flow.

To fully cover this bug, please add a companion test for a Classic-only project with ambient_resume: false in the config. That test should assert that the managed ambient resume block is absent (or removed if previously inserted) while preserving existing AGENTS.md/CLAUDE.md content, analogous to the existing test for projects that disable the probe, but in the Classic-only case.

Suggested implementation:

    expect(claude).toContain('<comet-ambient-resume>');
  });

  it('does not install ambient resume instructions for Classic-only projects with ambient_resume disabled', async () => {
    await arrangeClassicDocsOpenSpecUpdate(tmpDir, { ambientResume: false });

    // Pre-existing user content plus a previously inserted ambient resume block
    await fs.writeFile(
      path.join(tmpDir, 'AGENTS.md'),
      '# User\n\nKeep this.\n\n<comet-ambient-resume>\nPrevious content\n</comet-ambient-resume>\n',
      'utf8',
    );
    await fs.writeFile(
      path.join(tmpDir, 'CLAUDE.md'),
      '# User\n\nAlso keep this.\n\n<comet-ambient-resume>\nPrevious content\n</comet-ambient-resume>\n',
      'utf8',
    );

    const fakeHome = path.join(tmpDir, 'fake-home-classic-instructions-disabled');
    const homedirSpy = vi.spyOn(os, 'homedir').mockReturnValue(fakeHome);
    const log = vi.spyOn(console, 'log').mockImplementation(() => undefined);
    let json: string;

    try {
      await updateCommand(tmpDir, { json: true, skipNpm: true });
    } finally {
      homedirSpy.mockRestore();
      log.mockRestore();
    }

    const agents = await fs.readFile(path.join(tmpDir, 'AGENTS.md'), 'utf8');
    const claude = await fs.readFile(path.join(tmpDir, 'CLAUDE.md'), 'utf8');

    // The managed ambient resume block should be absent
    expect(agents).not.toContain('<comet-ambient-resume>');
    expect(claude).not.toContain('<comet-ambient-resume>');

    // User content should be preserved
    expect(agents).toContain('# User\n\nKeep this.\n');
    expect(claude).toContain('# User\n\nAlso keep this.\n');
  });

  it('installs ambient resume instructions for Classic-only projects', async () => {

To fully wire this up, ensure that Classic-only projects with ambient_resume: false are correctly arranged:

  1. Update arrangeClassicDocsOpenSpecUpdate (or introduce a new helper) to accept an options object with { ambientResume: boolean } and, when ambientResume === false, write the project config with ambient_resume: false in the Classic-only configuration.
  2. If your existing "projects that disable the probe" test uses a different helper (e.g. arrangeDocsOpenSpecUpdateWithAmbientResumeDisabled), you can alternatively create a Classic-specific variant (e.g. arrangeClassicDocsOpenSpecUpdateWithAmbientResumeDisabled(tmpDir)) and call that from this new test instead of passing an options objectβ€”keep the test body the same, only change the helper invocation.
  3. Confirm that any other tests calling arrangeClassicDocsOpenSpecUpdate are updated if you change its signature; if you add a new helper instead, no further changes should be required.


it('installs ambient resume instructions for Classic-only projects', async () => {
await arrangeClassicDocsOpenSpecUpdate(tmpDir);
await fs.writeFile(path.join(tmpDir, 'AGENTS.md'), '# User\n\nKeep this.\n', 'utf8');
await fs.writeFile(path.join(tmpDir, 'CLAUDE.md'), '# User\n\nAlso keep this.\n', 'utf8');

const fakeHome = path.join(tmpDir, 'fake-home-classic-instructions');
const homedirSpy = vi.spyOn(os, 'homedir').mockReturnValue(fakeHome);
const log = vi.spyOn(console, 'log').mockImplementation(() => undefined);
let json: string;
try {
await updateCommand(tmpDir, { json: true, skipNpm: true });
json = log.mock.calls.map((call) => call.join(' ')).join('\n');
} finally {
log.mockRestore();
homedirSpy.mockRestore();
}

const result = JSON.parse(json);
expect(result.projectInstructions.updated).toBe(2);

const agents = await fs.readFile(path.join(tmpDir, 'AGENTS.md'), 'utf8');
const claude = await fs.readFile(path.join(tmpDir, 'CLAUDE.md'), 'utf8');
for (const content of [agents, claude]) {
expect(content).toContain('<comet-ambient-resume>');
expect(content).toContain('comet resume-probe . --stdin --json');
}
expect(agents).toContain('# User\n\nKeep this.');
expect(claude).toContain('# User\n\nAlso keep this.');
});

it('removes ambient resume instructions when the project disables the probe', async () => {
await fs.mkdir(path.join(tmpDir, '.comet'), { recursive: true });
await fs.writeFile(
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-beta.20');
expect(packageJson.version).toBe('0.4.0-beta.19');
expect(packageLock.version).toBe(packageJson.version);
expect(packageLock.packages[''].version).toBe(packageJson.version);
expect(assetsManifest.version).toBe(packageJson.version);
Expand Down
Loading