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
2 changes: 2 additions & 0 deletions Documentation/getting-started/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ cratis init

Detection uses both the files a project already has and the environment variables each tool exports, so running `cratis init` from inside an assistant's own terminal configures that assistant even before the project has any of its files.

If the instruction file it would edit is generated from a shared corpus and propagated across repositories, pass `--no-context`. The skill and prompt files are still written; only the `@CHRONICLE.md` reference is left out, and `init` reports where to add it instead — editing a generated file would work until the next sync silently removed it.

The catalog is a snapshot taken when `init` runs. After upgrading the CLI it still describes the surface it was generated from, so `cratis init` reports the mismatch and points at `cratis init --refresh`, which re-captures it. For the same catalog as raw JSON, run `cratis llm-context` (add `--schema` for its JSON Schema).

## Recap
Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,11 @@ The catalog `init` writes is a snapshot, not a live lookup. After upgrading the
describes the surface it was generated from, so `init` says so and names `--refresh` as the fix
rather than leaving an agent to confidently call a command that has since changed.

If your instruction file (`AGENTS.md`, `CLAUDE.md`, `.github/copilot-instructions.md`) is generated
from a shared corpus and propagated, pass `--no-context`: the skill and prompt are still written,
and `init` tells you to add the `@CHRONICLE.md` line to whatever generates that file instead of
editing a file the next sync will overwrite.

## Tab completion asks the server

`cratis completions install` writes a completion script for bash, zsh, fish or PowerShell.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ void Establish()
File.WriteAllText(_agentsMd, "# House rules\n\n@CHRONICLE.md\n");
}

void Because() => AiToolConfigurator.Configure(AiTool.Pi, _tempDir, force: false, includeCommands: false, llmContextJson: "{}");
void Because() => AiToolConfigurator.Configure(AiTool.Pi, _tempDir, new(Force: false, IncludeCommands: false, IncludeContext: true, LlmContextJson: "{}"));

[Fact] void should_not_add_a_second_reference() =>
File.ReadAllText(_agentsMd).Split("@CHRONICLE.md").Length.ShouldEqual(2);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ void Establish()
Directory.CreateDirectory(_tempDir);
}

void Because() => _actions = AiToolConfigurator.Configure(AiTool.Pi, _tempDir, force: false, includeCommands: true, llmContextJson: "{}");
void Because() => _actions = AiToolConfigurator.Configure(AiTool.Pi, _tempDir, new(Force: false, IncludeCommands: true, IncludeContext: true, LlmContextJson: "{}"));

[Fact] void should_write_the_skill_where_pi_looks_for_it() =>
File.Exists(Path.Combine(_tempDir, ".pi", "skills", "chronicle-cli", "SKILL.md")).ShouldBeTrue();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright (c) Cratis. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace Cratis.Cli.for_InitCommand.when_the_context_file_is_generated;

/// <summary>
/// Some repositories generate their instruction files from a shared corpus and propagate them, so appending
/// to one is undone by the next sync - the reference disappears at an unpredictable later moment, which is
/// worse than never adding it. The skill and prompt are unaffected and still written.
/// <para>
/// The skip is reported rather than silent, because a project that looks configured and loads nothing is
/// the harder failure to notice.
/// </para>
/// </summary>
public class and_configuring_pi : Specification
{
string _tempDir;
string _agentsMd;
IReadOnlyList<string> _actions;

void Establish()
{
_tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
Directory.CreateDirectory(_tempDir);
_agentsMd = Path.Combine(_tempDir, "AGENTS.md");
File.WriteAllText(_agentsMd, "# Generated - do not edit\n");
}

void Because() => _actions = AiToolConfigurator.Configure(
AiTool.Pi,
_tempDir,
new(Force: false, IncludeCommands: true, IncludeContext: false, LlmContextJson: "{}"));

[Fact] void should_leave_the_generated_file_untouched() =>
File.ReadAllText(_agentsMd).ShouldEqual("# Generated - do not edit\n");

[Fact] void should_still_write_the_skill() =>
File.Exists(Path.Combine(_tempDir, ".pi", "skills", "chronicle-cli", "SKILL.md")).ShouldBeTrue();

[Fact] void should_still_write_the_prompt() =>
File.Exists(Path.Combine(_tempDir, ".pi", "prompts", "chronicle-diagnose.md")).ShouldBeTrue();

[Fact] void should_say_it_skipped_the_reference() =>
_actions.ShouldContain(_ => _.Contains("Skipped the @CHRONICLE.md reference in AGENTS.md", StringComparison.Ordinal));

void Destroy()
{
if (Directory.Exists(_tempDir))
{
Directory.Delete(_tempDir, true);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright (c) Cratis. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace Cratis.Cli.for_InitCommand.when_the_context_file_is_generated;

/// <summary>
/// Cursor and Windsurf are configured entirely through a rules file, so --no-context leaves them with
/// nothing to write. Reporting that is the whole value: silently doing nothing reads as success.
/// </summary>
public class and_the_tool_only_writes_a_context_file : Specification
{
string _tempDir;
IReadOnlyList<string> _cursor;
IReadOnlyList<string> _windsurf;

void Establish()
{
_tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
Directory.CreateDirectory(_tempDir);
}

void Because()
{
var configuration = new AiToolConfiguration(Force: true, IncludeCommands: true, IncludeContext: false, LlmContextJson: "{}");
_cursor = AiToolConfigurator.Configure(AiTool.Cursor, _tempDir, configuration);
_windsurf = AiToolConfigurator.Configure(AiTool.Windsurf, _tempDir, configuration);
}

[Fact] void should_not_write_the_cursor_rule() =>
File.Exists(Path.Combine(_tempDir, ".cursor", "rules", "chronicle.mdc")).ShouldBeFalse();

[Fact] void should_not_write_the_windsurf_rules() =>
File.Exists(Path.Combine(_tempDir, ".windsurfrules")).ShouldBeFalse();

[Fact] void should_say_why_cursor_got_nothing() =>
_cursor.ShouldContain(_ => _.Contains("Skipped the @CHRONICLE.md reference", StringComparison.Ordinal));

[Fact] void should_say_why_windsurf_got_nothing() =>
_windsurf.ShouldContain(_ => _.Contains("Skipped the @CHRONICLE.md reference", StringComparison.Ordinal));

void Destroy()
{
if (Directory.Exists(_tempDir))
{
Directory.Delete(_tempDir, true);
}
}
}
24 changes: 24 additions & 0 deletions Source/Cli/Commands/Init/AiToolConfiguration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright (c) Cratis. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace Cratis.Cli.Commands.Init;

/// <summary>
/// What <c>cratis init</c> should write for one AI tool.
/// </summary>
/// <remarks>
/// A record rather than a parameter list because the three switches all read as bare booleans at the call
/// site, where transposing two of them produces a configuration that looks configured and is not.
/// </remarks>
/// <param name="Force">Whether to overwrite files that already exist.</param>
/// <param name="IncludeCommands">Whether to write the skill and slash-command/prompt files.</param>
/// <param name="IncludeContext">
/// Whether to add the <c>@CHRONICLE.md</c> reference to the tool's instruction file. False when that file is
/// generated from a shared corpus, where the edit would be overwritten on the next sync.
/// </param>
/// <param name="LlmContextJson">The serialized llm-context JSON to embed in skill files.</param>
public record AiToolConfiguration(
bool Force,
bool IncludeCommands,
bool IncludeContext,
string LlmContextJson);
Loading
Loading