Skip to content

Commit 68bd143

Browse files
reneenoblegithub-actions[bot]aaronpowell
authored
Fix broken links beginners cli course sync (#1263)
* chore: publish from staged * Update instructions for converting links from original repo * Correct existing broken links * chore: retrigger ci * cleaning up marerialzed plugins * Fixing clean script to sort out plugin.json file too * Fixing readme * Fixing plugin.json drift * Fixing readme --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Aaron Powell <me@aaron-powell.com>
1 parent 1126783 commit 68bd143

File tree

4 files changed

+129
-15
lines changed

4 files changed

+129
-15
lines changed

.github/workflows/cli-for-beginners-sync.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ For each local file that needs updating:
9393
- Preserve upstream wording, headings, section order, assignments, and overall chapter flow as closely as practical
9494
- Do not summarize, reinterpret, or "website-optimize" the course into a different learning experience
9595
- Only adapt what the website requires: Astro frontmatter, route-safe internal links, GitHub repo links, local asset paths, and minor HTML/CSS hooks needed for presentation
96+
- Convert repo-root relative links that are invalid on the published website (for example `../.github/agents/`, `./.github/...`, or `.github/...`) into absolute links to `https://github.com/github/copilot-cli-for-beginners` (use `/tree/main/...` for directories and `/blob/main/...` for files)
9697

9798
3. If upstream adds, removes, or renames major sections or chapters:
9899
- Create, delete, or rename the corresponding markdown files in `website/src/content/docs/learning-hub/cli-for-beginners/`

eng/clean-materialized-plugins.mjs

Lines changed: 119 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,73 @@
22

33
import fs from "fs";
44
import path from "path";
5+
import { fileURLToPath } from "url";
56
import { ROOT_FOLDER } from "./constants.mjs";
67

78
const PLUGINS_DIR = path.join(ROOT_FOLDER, "plugins");
8-
const MATERIALIZED_DIRS = ["agents", "commands", "skills"];
9+
const MATERIALIZED_SPECS = {
10+
agents: {
11+
path: "agents",
12+
restore(dirPath) {
13+
return collectFiles(dirPath).map((relativePath) => `./agents/${relativePath}`);
14+
},
15+
},
16+
commands: {
17+
path: "commands",
18+
restore(dirPath) {
19+
return collectFiles(dirPath).map((relativePath) => `./commands/${relativePath}`);
20+
},
21+
},
22+
skills: {
23+
path: "skills",
24+
restore(dirPath) {
25+
return collectSkillDirectories(dirPath).map((relativePath) => `./skills/${relativePath}/`);
26+
},
27+
},
28+
};
29+
30+
export function restoreManifestFromMaterializedFiles(pluginPath) {
31+
const pluginJsonPath = path.join(pluginPath, ".github/plugin", "plugin.json");
32+
if (!fs.existsSync(pluginJsonPath)) {
33+
return false;
34+
}
35+
36+
let plugin;
37+
try {
38+
plugin = JSON.parse(fs.readFileSync(pluginJsonPath, "utf8"));
39+
} catch (error) {
40+
throw new Error(`Failed to parse ${pluginJsonPath}: ${error.message}`);
41+
}
42+
43+
let changed = false;
44+
for (const [field, spec] of Object.entries(MATERIALIZED_SPECS)) {
45+
const materializedPath = path.join(pluginPath, spec.path);
46+
if (!fs.existsSync(materializedPath) || !fs.statSync(materializedPath).isDirectory()) {
47+
continue;
48+
}
49+
50+
const restored = spec.restore(materializedPath);
51+
if (!arraysEqual(plugin[field], restored)) {
52+
plugin[field] = restored;
53+
changed = true;
54+
}
55+
}
56+
57+
if (changed) {
58+
fs.writeFileSync(pluginJsonPath, JSON.stringify(plugin, null, 2) + "\n", "utf8");
59+
}
60+
61+
return changed;
62+
}
963

1064
function cleanPlugin(pluginPath) {
65+
const manifestUpdated = restoreManifestFromMaterializedFiles(pluginPath);
66+
if (manifestUpdated) {
67+
console.log(` Updated ${path.basename(pluginPath)}/.github/plugin/plugin.json`);
68+
}
69+
1170
let removed = 0;
12-
for (const subdir of MATERIALIZED_DIRS) {
71+
for (const { path: subdir } of Object.values(MATERIALIZED_SPECS)) {
1372
const target = path.join(pluginPath, subdir);
1473
if (fs.existsSync(target) && fs.statSync(target).isDirectory()) {
1574
const count = countFiles(target);
@@ -18,7 +77,8 @@ function cleanPlugin(pluginPath) {
1877
console.log(` Removed ${path.basename(pluginPath)}/${subdir}/ (${count} files)`);
1978
}
2079
}
21-
return removed;
80+
81+
return { removed, manifestUpdated };
2282
}
2383

2484
function countFiles(dir) {
@@ -33,6 +93,49 @@ function countFiles(dir) {
3393
return count;
3494
}
3595

96+
function collectFiles(dir, rootDir = dir) {
97+
const files = [];
98+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
99+
const entryPath = path.join(dir, entry.name);
100+
if (entry.isDirectory()) {
101+
files.push(...collectFiles(entryPath, rootDir));
102+
} else {
103+
files.push(toPosixPath(path.relative(rootDir, entryPath)));
104+
}
105+
}
106+
return files.sort();
107+
}
108+
109+
function collectSkillDirectories(dir, rootDir = dir) {
110+
const skillDirs = [];
111+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
112+
if (!entry.isDirectory()) {
113+
continue;
114+
}
115+
116+
const entryPath = path.join(dir, entry.name);
117+
if (fs.existsSync(path.join(entryPath, "SKILL.md"))) {
118+
skillDirs.push(toPosixPath(path.relative(rootDir, entryPath)));
119+
continue;
120+
}
121+
122+
skillDirs.push(...collectSkillDirectories(entryPath, rootDir));
123+
}
124+
return skillDirs.sort();
125+
}
126+
127+
function arraysEqual(left, right) {
128+
if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) {
129+
return false;
130+
}
131+
132+
return left.every((value, index) => value === right[index]);
133+
}
134+
135+
function toPosixPath(filePath) {
136+
return filePath.split(path.sep).join("/");
137+
}
138+
36139
function main() {
37140
console.log("Cleaning materialized files from plugins...\n");
38141

@@ -47,16 +150,26 @@ function main() {
47150
.sort();
48151

49152
let total = 0;
153+
let manifestsUpdated = 0;
50154
for (const dirName of pluginDirs) {
51-
total += cleanPlugin(path.join(PLUGINS_DIR, dirName));
155+
const { removed, manifestUpdated } = cleanPlugin(path.join(PLUGINS_DIR, dirName));
156+
total += removed;
157+
if (manifestUpdated) {
158+
manifestsUpdated++;
159+
}
52160
}
53161

54162
console.log();
55-
if (total === 0) {
163+
if (total === 0 && manifestsUpdated === 0) {
56164
console.log("✅ No materialized files found. Plugins are already clean.");
57165
} else {
58166
console.log(`✅ Removed ${total} materialized file(s) from plugins.`);
167+
if (manifestsUpdated > 0) {
168+
console.log(`✅ Updated ${manifestsUpdated} plugin manifest(s) with folder trailing slashes.`);
169+
}
59170
}
60171
}
61172

62-
main();
173+
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
174+
main();
175+
}

website/src/content/docs/learning-hub/cli-for-beginners/04-agents-and-custom-instructions.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ Never used or made an agent? Here's all you need to know to get started for this
6060
```
6161
This invokes the Plan agent to create a step-by-step implementation plan.
6262
63-
2. **See one of our custom agent examples:** It's simple to define an agent's instructions, look at our provided [python-reviewer.agent.md](https://github.com/github/copilot-cli-for-beginners/blob/main/github/agents/python-reviewer.agent.md) file to see the pattern.
63+
2. **See one of our custom agent examples:** It's simple to define an agent's instructions, look at our provided [python-reviewer.agent.md](https://github.com/github/copilot-cli-for-beginners/blob/main/.github/agents/python-reviewer.agent.md) file to see the pattern.
6464
6565
3. **Understand the core concept:** Agents are like consulting a specialist instead of a generalist. A "frontend agent" will focus on accessibility and component patterns automatically, you don't have to remind it because it is already specified in the agent's instructions.
6666
@@ -148,7 +148,7 @@ When reviewing code, always check for:
148148
| `.github/agents/` | Project-specific | Team-shared agents with project conventions |
149149
| `~/.copilot/agents/` | Global (all projects) | Personal agents you use everywhere |
150150
151-
**This project includes sample agent files in the [.github/agents/](../.github/agents/) folder**. You can write your own, or customize the ones already provided.
151+
**This project includes sample agent files in the [.github/agents/](https://github.com/github/copilot-cli-for-beginners/tree/main/.github/agents/) folder**. You can write your own, or customize the ones already provided.
152152
153153
<details>
154154
<summary>📂 See the sample agents in this course</summary>
@@ -534,10 +534,10 @@ Use these names in the `tools` list:
534534

535535
> 💡 **Note for beginners**: The examples below are templates. **Replace the specific technologies with whatever your project uses.** The important thing is the *structure* of the agent, not the specific technologies mentioned.
536536

537-
This project includes working examples in the [.github/agents/](../.github/agents/) folder:
538-
- [hello-world.agent.md](https://github.com/github/copilot-cli-for-beginners/blob/main/github/agents/hello-world.agent.md) - Minimal example, start here
539-
- [python-reviewer.agent.md](https://github.com/github/copilot-cli-for-beginners/blob/main/github/agents/python-reviewer.agent.md) - Python code quality reviewer
540-
- [pytest-helper.agent.md](https://github.com/github/copilot-cli-for-beginners/blob/main/github/agents/pytest-helper.agent.md) - Pytest testing specialist
537+
This project includes working examples in the [.github/agents/](https://github.com/github/copilot-cli-for-beginners/tree/main/.github/agents/) folder:
538+
- [hello-world.agent.md](https://github.com/github/copilot-cli-for-beginners/blob/main/.github/agents/hello-world.agent.md) - Minimal example, start here
539+
- [python-reviewer.agent.md](https://github.com/github/copilot-cli-for-beginners/blob/main/.github/agents/python-reviewer.agent.md) - Python code quality reviewer
540+
- [pytest-helper.agent.md](https://github.com/github/copilot-cli-for-beginners/blob/main/.github/agents/pytest-helper.agent.md) - Pytest testing specialist
541541

542542
For community agents, see [github/awesome-copilot](https://github.com/github/awesome-copilot).
543543

website/src/content/docs/learning-hub/cli-for-beginners/05-skills.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ Learn what skills are, why they matter, and how they differ from agents and MCP.
6464
```
6565
This shows all skills Copilot can find in your project and personal folders.
6666

67-
2. **Look at a real skill file:** Check out our provided [code-checklist SKILL.md](https://github.com/github/copilot-cli-for-beginners/blob/main/github/skills/code-checklist/SKILL.md) to see the pattern. It's just YAML frontmatter plus markdown instructions.
67+
2. **Look at a real skill file:** Check out our provided [code-checklist SKILL.md](https://github.com/github/copilot-cli-for-beginners/blob/main/.github/skills/code-checklist/SKILL.md) to see the pattern. It's just YAML frontmatter plus markdown instructions.
6868

6969
3. **Understand the core concept:** Skills are task-specific instructions that Copilot loads *automatically* when your prompt matches the skill's description. You don't need to activate them, just ask naturally.
7070

@@ -91,7 +91,7 @@ copilot
9191

9292
> 💡 **Key Insight**: Skills are **automatically triggered** based on your prompt matching the skill's description. Just ask naturally and Copilot applies relevant skills behind the scenes. You can also invoke skills directly as well which you'll learn about next.
9393
94-
> 🧰 **Ready-to-use templates**: Check out the [.github/skills](../.github/skills/) folder for simple copy-paste skills you can try out.
94+
> 🧰 **Ready-to-use templates**: Check out the [.github/skills](https://github.com/github/copilot-cli-for-beginners/tree/main/.github/skills/) folder for simple copy-paste skills you can try out.
9595
9696
### Direct Slash Command Invocation
9797

@@ -591,7 +591,7 @@ Apply what you've learned by building and testing your own skills.
591591

592592
### Build More Skills
593593

594-
Here are two more skills showing different patterns. Follow the same `mkdir` + `cat` workflow from "Creating Your First Skill" above or copy and paste the skills into the proper location. More examples are available in [.github/skills](../.github/skills).
594+
Here are two more skills showing different patterns. Follow the same `mkdir` + `cat` workflow from "Creating Your First Skill" above or copy and paste the skills into the proper location. More examples are available in [.github/skills](https://github.com/github/copilot-cli-for-beginners/tree/main/.github/skills).
595595

596596
### pytest Test Generation Skill
597597

0 commit comments

Comments
 (0)