Skip to content
Closed
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
35 changes: 24 additions & 11 deletions 02-context-conversations/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ copilot
### Basic @ Patterns

| Pattern | What It Does | Example Use |
|---------|--------------|-------------|
| --- | --- | --- |
| `@file.py` | Reference a single file | `Review @samples/book-app-project/books.py` |
| `@folder/` | Reference all files in a directory | `Review @samples/book-app-project/` |
| `@file1.py @file2.py` | Reference multiple files | `Compare @samples/book-app-project/book_app.py @samples/book-app-project/books.py` |
Expand Down Expand Up @@ -475,26 +475,27 @@ No re-explaining. No re-reading files. Just continue working.

<img src="assets/optional-going-deeper.png" alt="Abstract crystal cave in blue and purple tones representing deeper exploration of context concepts" width="800"/>

These topics build on the essentials above. **Pick what interests you, or skip ahead to [Practice](#practice).**
These topics build on the essentials above. **Pick what interests you, or skip ahead to** [**Practice**](#practice)**.**

| I want to learn about... | Jump to |
|---|---|
| --- | --- |
| Wildcard patterns and advanced session commands | [Additional @ Patterns & Session Commands](#additional-patterns) |
| Building on context across multiple prompts | [Context-Aware Conversations](#context-aware-conversations) |
| Token limits and `/compact` | [Understanding Context Windows](#understanding-context-windows) |
| How to pick the right files to reference | [Choosing What to Reference](#choosing-what-to-reference) |
| Analyzing screenshots and mockups | [Working with Images](#working-with-images) |

<details>
<summary><strong>Additional @ Patterns & Session Commands</strong></summary>
<summary><strong>Additional @ Patterns &amp; Session Commands</strong></summary>

<a id="additional-patterns"></a>

### Additional @ Patterns

For power users, Copilot CLI supports wildcard patterns and image references:

| Pattern | What It Does |
|---------|--------------|
| --- | --- |
| `@folder/*.py` | All .py files in folder |
| `@**/test_*.py` | Recursive wildcard: find all test files anywhere |
| `@image.png` | Image file for UI review |
Expand Down Expand Up @@ -537,6 +538,7 @@ copilot

<details>
<summary><strong>Context-Aware Conversations</strong></summary>

<a id="context-aware-conversations"></a>

### Context-Aware Conversations
Expand Down Expand Up @@ -577,6 +579,7 @@ Notice how each prompt builds on the previous work. This is the power of context

<details>
<summary><strong>Understanding Context Windows</strong></summary>

<a id="understanding-context-windows"></a>

### Understanding Context Windows
Expand Down Expand Up @@ -637,7 +640,7 @@ copilot
#### Context Efficiency Tips

| Situation | Action | Why |
|-----------|--------|-----|
| --- | --- | --- |
| Starting new topic | `/clear` | Removes irrelevant context |
| Went down wrong path | `/rewind` | Roll back conversation (and optionally restore files) to any earlier point |
| Long conversation | `/compact` | Summarizes history, frees tokens |
Expand All @@ -656,6 +659,7 @@ copilot

<details>
<summary><strong>Choosing What to Reference</strong></summary>

<a id="choosing-what-to-reference"></a>

### Choosing What to Reference
Expand All @@ -665,13 +669,14 @@ Not all files are equal when it comes to context. Here's how to choose wisely:
#### File Size Considerations

| File Size | Approximate [Tokens](../GLOSSARY.md#token) | Strategy |
|-----------|-------------------|----------|
| --- | --- | --- |
| Small (<100 lines) | ~500-1,500 tokens | Reference freely |
| Medium (100-500 lines) | ~1,500-7,500 tokens | Reference specific files |
| Large (500+ lines) | 7,500+ tokens | Be selective, use specific files |
| Very Large (1000+ lines) | 15,000+ tokens | Consider splitting or targeting sections |

**Concrete examples:**

- The book app's 4 Python files combined ≈ 2,000-3,000 tokens
- A typical Python module (200 lines) ≈ 3,000 tokens
- A Flask API file (400 lines) ≈ 6,000 tokens
Expand All @@ -683,13 +688,15 @@ Not all files are equal when it comes to context. Here's how to choose wisely:
#### What to Include vs. Exclude

**High value** (include these):

- Entry points (`book_app.py`, `main.py`, `app.py`)
- The specific files you're asking about
- Files directly imported by your target file
- Configuration files (`requirements.txt`, `pyproject.toml`)
- Data models or dataclasses

**Lower value** (consider excluding):

- Generated files (compiled output, bundled assets)
- Node modules or vendor directories
- Large data files or fixtures
Expand All @@ -706,11 +713,13 @@ Less specific ──────────────────────
```

**When to go broad** (`@samples/book-app-project/`):

- Initial codebase exploration
- Finding patterns across many files
- Architecture reviews

**When to go specific** (`@samples/book-app-project/books.py`):

- Debugging a particular issue
- Code review of a specific file
- Asking about a single function
Expand Down Expand Up @@ -739,6 +748,7 @@ This staged approach keeps context focused and efficient.

<details>
<summary><strong>Working with Images</strong></summary>

<a id="working-with-images"></a>

### Working with Images
Expand Down Expand Up @@ -847,6 +857,7 @@ The hands-on examples focused on code quality reviews and input validation. Now
<summary>💡 Hints (click to expand)</summary>

**Getting started:**

```bash
cd /path/to/copilot-cli-for-beginners
copilot
Expand All @@ -859,6 +870,7 @@ copilot
Then resume with: `copilot --continue`

**Useful commands:**

- `@file.py` - Reference a single file
- `@folder/` - Reference all files in a folder (note the trailing `/`)
- `/context` - Check how much context you're using
Expand All @@ -877,12 +889,12 @@ Then resume with: `copilot --continue`
---

<details>
<summary>🔧 <strong>Common Mistakes & Troubleshooting</strong> (click to expand)</summary>
<summary>🔧 <strong>Common Mistakes &amp; Troubleshooting</strong> (click to expand)</summary>

### Common Mistakes

| Mistake | What Happens | Fix |
|---------|--------------|-----|
| --- | --- | --- |
| Forgetting `@` before filenames | Copilot CLI treats "books.py" as plain text | Use `@samples/book-app-project/books.py` to reference files |
| Expecting sessions to persist automatically | Starting `copilot` fresh loses all previous context | Use `--continue` (last session) or `--resume` (pick a session) |
| Referencing files outside current directory | "Permission denied" or "File not found" errors | Use `/add-dir /path/to/directory` to grant access |
Expand Down Expand Up @@ -912,6 +924,7 @@ copilot --add-dir /path/to/directory
```

**Context fills up too quickly**:

- Be more specific with file references
- Use `/clear` between different topics
- Split work across multiple sessions
Expand Down Expand Up @@ -942,7 +955,7 @@ copilot --add-dir /path/to/directory

Now that you can give Copilot CLI context, let's put it to work on real development tasks. The context techniques you just learned (file references, cross-file analysis, and session management) are the foundation for the powerful workflows in the next chapter.

In **[Chapter 03: Development Workflows](../03-development-workflows/README.md)**, you'll learn:
In [**Chapter 03: Development Workflows**](../03-development-workflows/README.md), you'll learn:

- Code review workflows
- Refactoring patterns
Expand All @@ -952,4 +965,4 @@ In **[Chapter 03: Development Workflows](../03-development-workflows/README.md)*

---

**[← Back to Chapter 01](../01-setup-and-first-steps/README.md)** | **[Continue to Chapter 03 →](../03-development-workflows/README.md)**
[**← Back to Chapter 01**](../01-setup-and-first-steps/README.md) | [**Continue to Chapter 03 →**](../03-development-workflows/README.md)
33 changes: 29 additions & 4 deletions samples/book-app-project-cs/Services/BookCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ private void LoadBooks()
try
{
var json = File.ReadAllText(_dataFile);
_books = JsonSerializer.Deserialize<List<Book>>(json, JsonOptions) ?? [];
var books = JsonSerializer.Deserialize<List<Book>>(json, JsonOptions);
_books = books ?? [];
}
catch (FileNotFoundException)
{
Expand All @@ -42,13 +43,31 @@ private void LoadBooks()

private void SaveBooks()
{
var directory = Path.GetDirectoryName(_dataFile);
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}

var json = JsonSerializer.Serialize(_books, JsonOptions);
File.WriteAllText(_dataFile, json);
}

public Book AddBook(string title, string author, int year)
{
var book = new Book { Title = title, Author = author, Year = year };
var cleanTitle = title.Trim();
var cleanAuthor = author.Trim();
if (string.IsNullOrWhiteSpace(cleanTitle) || string.IsNullOrWhiteSpace(cleanAuthor))
{
throw new ArgumentException("Title and author are required.");
}

if (year <= 0)
{
throw new ArgumentOutOfRangeException(nameof(year), "Year must be a positive integer.");
}

var book = new Book { Title = cleanTitle, Author = cleanAuthor, Year = year };
_books.Add(book);
SaveBooks();
return book;
Expand All @@ -58,7 +77,7 @@ public Book AddBook(string title, string author, int year)

public Book? FindBookByTitle(string title)
{
return _books.Find(b => b.Title.Equals(title, StringComparison.OrdinalIgnoreCase));
return _books.Find(b => b.Title.Equals(title.Trim(), StringComparison.OrdinalIgnoreCase));
}

public bool MarkAsRead(string title)
Expand All @@ -81,8 +100,14 @@ public bool RemoveBook(string title)

public List<Book> FindByAuthor(string author)
{
var normalized = author.Trim();
if (string.IsNullOrWhiteSpace(normalized))
{
return [];
}

return _books
.Where(b => b.Author.Equals(author, StringComparison.OrdinalIgnoreCase))
.Where(b => b.Author.Contains(normalized, StringComparison.OrdinalIgnoreCase))
.ToList();
}
}
38 changes: 32 additions & 6 deletions samples/book-app-project-js/books.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ class Book {
}

class BookCollection {
constructor(dataFile) {
this.dataFile = dataFile || DATA_FILE;
constructor(dataFile = DATA_FILE) {
this.dataFile = dataFile;
this.books = [];
this.loadBooks();
}
Expand All @@ -23,7 +23,13 @@ class BookCollection {
try {
const raw = fs.readFileSync(this.dataFile, "utf-8");
const data = JSON.parse(raw);
this.books = data.map((b) => new Book(b.title, b.author, b.year, b.read));
if (!Array.isArray(data)) {
this.books = [];
return;
}
this.books = data
.filter((item) => item && typeof item === "object")
.map((b) => new Book(b.title, b.author, b.year, !!b.read));
} catch (err) {
if (err.code === "ENOENT") {
this.books = [];
Expand All @@ -37,6 +43,10 @@ class BookCollection {
}

saveBooks() {
const dir = path.dirname(this.dataFile);
if (dir && !fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
const data = this.books.map((b) => ({
title: b.title,
author: b.author,
Expand All @@ -47,7 +57,18 @@ class BookCollection {
}

addBook(title, author, year) {
const book = new Book(title, author, year);
const cleanTitle = String(title).trim();
const cleanAuthor = String(author).trim();
if (!cleanTitle || !cleanAuthor) {
throw new Error("Title and author are required.");
}

const numericYear = Number.parseInt(year, 10);
if (Number.isNaN(numericYear) || numericYear <= 0) {
throw new Error("Year must be a positive integer.");
}

const book = new Book(cleanTitle, cleanAuthor, numericYear);
this.books.push(book);
this.saveBooks();
return book;
Expand All @@ -58,7 +79,8 @@ class BookCollection {
}

findBookByTitle(title) {
return this.books.find((b) => b.title.toLowerCase() === title.toLowerCase()) || null;
const normalizedTitle = String(title).trim().toLowerCase();
return this.books.find((b) => b.title.trim().toLowerCase() === normalizedTitle) || null;
}

markAsRead(title) {
Expand All @@ -82,7 +104,11 @@ class BookCollection {
}

findByAuthor(author) {
return this.books.filter((b) => b.author.toLowerCase() === author.toLowerCase());
const normalizedAuthor = String(author).trim().toLowerCase();
if (!normalizedAuthor) {
return [];
}
return this.books.filter((b) => b.author.trim().toLowerCase().includes(normalizedAuthor));
}
}

Expand Down
17 changes: 17 additions & 0 deletions samples/book-app-project-js/tests/test_books.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,21 @@ describe("BookCollection", () => {
const result = collection.removeBook("Nonexistent Book");
assert.equal(result, false);
});

it("should find books by partial author match", () => {
const collection = new BookCollection(tempFile);
collection.addBook("Dune", "Frank Herbert", 1965);
collection.addBook("The Hobbit", "J.R.R. Tolkien", 1937);

const matches = collection.findByAuthor("tolkien");

assert.deepEqual(matches.map((book) => book.title), ["The Hobbit"]);
});

it("should validate required data before adding a book", () => {
const collection = new BookCollection(tempFile);

assert.throws(() => collection.addBook(" ", "George Orwell", 1949), /Title and author are required/);
assert.throws(() => collection.addBook("1984", "George Orwell", 0), /Year must be a positive integer/);
});
});
Loading