Skip to content

Commit 0f22fc1

Browse files
authored
Merge pull request #940 from puppetlabs/claude
Add `CLAUDE.md` for AI assistant guidance
2 parents 7fcb91f + a540f96 commit 0f22fc1

7 files changed

Lines changed: 184 additions & 0 deletions

File tree

.claude/hooks/_parse_input.sh

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/usr/bin/env bash
2+
# Shared helper: reads hook JSON from stdin and sets $cmd to the Bash tool command.
3+
# Sources into each hook with: . "$(dirname "$0")/_parse_input.sh"
4+
# Exits 0 (allow) if no JSON parser is available.
5+
6+
if command -v jq >/dev/null 2>&1; then
7+
# shellcheck disable=SC2034 # cmd is used by the sourcing hook script
8+
cmd=$(jq -r '.tool_input.command // ""' 2>/dev/null || echo "")
9+
elif command -v python3 >/dev/null 2>&1; then
10+
# shellcheck disable=SC2034 # cmd is used by the sourcing hook script
11+
cmd=$(python3 -c "import sys,json; print(json.load(sys.stdin).get('tool_input',{}).get('command',''))" 2>/dev/null || echo "")
12+
else
13+
exit 0
14+
fi

.claude/hooks/no-main-commits.sh

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#!/usr/bin/env bash
2+
# Project rule: never work directly on the main or master branch.
3+
# Blocks: git commit when current branch is main or master.
4+
5+
# shellcheck source=/dev/null
6+
. "$(dirname "$0")/_parse_input.sh"
7+
8+
if echo "$cmd" | grep -qE '(^|[;&|`(])[[:space:]]*(([[:alpha:]_][[:alnum:]_]*=[^[:space:]]*[[:space:]]+)*)([^[:space:]]*/)?git[[:space:]]+commit([[:space:]]|$|[;&|])'; then
9+
branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null)
10+
if [ "$branch" = "main" ] || [ "$branch" = "master" ]; then
11+
echo "{\"continue\":false,\"stopReason\":\"Project rule: never work directly on the $branch branch.\"}"
12+
fi
13+
fi

.claude/hooks/no-pr-merge.sh

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#!/usr/bin/env bash
2+
# Project rule: never merge a pull request.
3+
# Blocks: gh pr merge
4+
5+
# shellcheck source=/dev/null
6+
. "$(dirname "$0")/_parse_input.sh"
7+
8+
if echo "$cmd" | grep -qE '(^|[;&|])[[:space:]]*gh pr merge'; then
9+
echo '{"continue":false,"stopReason":"Project rule: never merge a pull request."}'
10+
fi

.claude/hooks/no-push.sh

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#!/usr/bin/env bash
2+
# Project rule: never push a branch without explicit instruction.
3+
# Blocks: git push
4+
5+
# shellcheck source=/dev/null
6+
. "$(dirname "$0")/_parse_input.sh"
7+
8+
if echo "$cmd" | grep -qE '(^|[;&|`(])[[:space:]]*(([[:alpha:]_][[:alnum:]_]*=[^[:space:]]*[[:space:]]+)*)([^[:space:]]*/)?git[[:space:]]+push([[:space:]]|$|[;&|])'; then
9+
echo '{"continue":false,"stopReason":"Project rule: never push a branch without explicit instruction."}'
10+
fi

.claude/hooks/no-rm.sh

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#!/usr/bin/env bash
2+
# Project rule: never delete a file without explicit permission.
3+
# Blocks: rm, sudo rm, xargs rm, /bin/rm, /usr/bin/rm (and similar absolute paths).
4+
# Does not cover: find -exec rm (rm as a subprocess argument, not a shell token).
5+
6+
# shellcheck source=/dev/null
7+
. "$(dirname "$0")/_parse_input.sh"
8+
9+
if echo "$cmd" | grep -qE '(^|[;&|])[[:space:]]*(sudo[[:space:]]+|xargs[[:space:]]+)?([^[:space:]]*/)?rm([[:space:]]|$)'; then
10+
echo '{"continue":false,"stopReason":"Project rule: never delete a file without explicit permission."}'
11+
fi

.claude/settings.json

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"hooks": {
3+
"PreToolUse": [
4+
{
5+
"matcher": "Bash",
6+
"hooks": [
7+
{
8+
"type": "command",
9+
"command": ".claude/hooks/no-pr-merge.sh",
10+
"timeout": 5,
11+
"statusMessage": "Checking project rules..."
12+
},
13+
{
14+
"type": "command",
15+
"command": ".claude/hooks/no-main-commits.sh",
16+
"timeout": 5
17+
},
18+
{
19+
"type": "command",
20+
"command": ".claude/hooks/no-push.sh",
21+
"timeout": 5
22+
},
23+
{
24+
"type": "command",
25+
"command": ".claude/hooks/no-rm.sh",
26+
"timeout": 5
27+
}
28+
]
29+
}
30+
]
31+
}
32+
}

CLAUDE.md

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Overview
6+
7+
This is the official **Puppet extension for Visual Studio Code** — a TypeScript VSCode extension client. It does *not* contain the Puppet language intelligence itself; that lives in the [Puppet Editor Services](https://github.com/puppetlabs/puppet-editor-services) language server (Ruby), which is downloaded and bundled at build time ("vendored"). This repo is primarily a Language Server Protocol (LSP) client plus VSCode-specific features (commands, views, debugging, status bar).
8+
9+
## Build & Development
10+
11+
The build is driven by **psake** (PowerShell), not plain npm. PowerShell (`pwsh`) and the `psake` module are required. The npm `build`/`watch` scripts shell out to `build.ps1`.
12+
13+
```bash
14+
npm install # install node deps
15+
npm run build # full build: npm install + vendor + tsc (via build.ps1 -> psakefile.ps1)
16+
npm run compile # TypeScript compile only (tsc -p ./), output to ./out
17+
npm run watch # build once, then tsc -watch
18+
./build.ps1 -task clean,vendor # re-download the language server + syntax (see Vendoring below)
19+
```
20+
21+
To run/debug the extension interactively: open the repo in VSCode and press **F5** (launches an Extension Development Host). Vendored resources must exist first (`./build.ps1 -task vendor`).
22+
23+
## Lint, Format, Test
24+
25+
```bash
26+
npm run lint # eslint --ext .ts src
27+
npm run fix # eslint --fix
28+
npm run format # prettier --write on **/*.{ts,js,json}
29+
npm test # compiles, then runs integration tests in a headless VSCode (out/test/runtest.js)
30+
npm run test:coverage # same, with nyc/lcov coverage
31+
```
32+
33+
Tests are **VSCode integration tests** (Mocha, `tdd` UI, chai + sinon), launched via `@vscode/test-electron` which downloads a real VSCode and runs the suite inside it — there is no pure-unit `mocha` runner. [src/test/runtest.ts](src/test/runtest.ts) is the entry point; [src/test/suite/index.ts](src/test/suite/index.ts) globs `**/*.test.js` from the compiled `out/test` dir. Because tests run compiled JS, **you must `npm run compile` (or `npm test` which does it via `pretest`) before test changes take effect.** There is no built-in single-test filter in the npm scripts; narrow runs by temporarily using Mocha's `.only` or commenting the glob.
34+
35+
Note: many tests resolve a real PDK/Puppet install path; CI installs the PDK on each OS before running (see [.github/workflows/vscode-ci.yml](.github/workflows/vscode-ci.yml)). The full suite is run across Linux/Windows/macOS.
36+
37+
## Vendoring (important architecture detail)
38+
39+
The language server and syntax grammar are **not committed** — they are fetched into `vendor/` during the build. [psakefile.ps1](psakefile.ps1) reads the `editorComponents` block in [package.json](package.json) to decide what to fetch:
40+
41+
- `editorServices.release` / `editorSyntax.release` — download a tagged GitHub release (default).
42+
- `githubref` / `githubuser` / `githubrepo` — fetch from a specific repo/branch instead.
43+
- `directory` — copy from a local checkout (useful when developing the language server alongside this extension).
44+
45+
The `VendorEditorServices` / `VendorEditorSyntax` tasks have preconditions that skip if `vendor/languageserver` or `syntaxes/puppet.tmLanguage` already exist, so run `clean` first to force a refresh. See [README_BUILD.md](README_BUILD.md) for all options. Cytoscape (used by the node graph) is also vendored from `node_modules`.
46+
47+
## Code Architecture
48+
49+
The extension entry point is [src/extension.ts](src/extension.ts) (`activate`/`deactivate`). Activation flow:
50+
51+
1. Reads VSCode workspace settings → `ISettings` ([src/settings.ts](src/settings.ts)), warns on legacy/deprecated settings.
52+
2. Builds an **`IAggregateConfiguration`** ([src/configuration.ts](src/configuration.ts)) — this resolves the abstract settings into concrete paths: which Puppet install to use (PDK vs Agent, `auto`-detected), Ruby dirs, RUBYLIB/PATH env, SSL paths, and PDK Ruby instance discovery ([src/configuration/pdkResolver.ts](src/configuration/pdkResolver.ts), [src/configuration/pathResolver.ts](src/configuration/pathResolver.ts)). This is the single source of truth for "how do I invoke the bundled Ruby".
53+
3. Constructs a list of **Features** and a **ConnectionHandler**.
54+
55+
### Features
56+
57+
Every discrete capability is an `IFeature` (a `vscode.Disposable`, [src/feature.ts](src/feature.ts)). They live in [src/feature/](src/feature/) and are instantiated into `extensionFeatures[]` in `activate()`, then all disposed in `deactivate()`. To add a capability, create a new `IFeature` and push it onto that array. Examples: `PDKFeature` (PDK commands), `DebuggingFeature`, `FormatDocumentFeature`, `PuppetNodeGraphFeature`, `PuppetStatusBarFeature`, the Puppetfile hover/completion features.
58+
59+
### ConnectionHandler (LSP client)
60+
61+
[src/handler.ts](src/handler.ts) defines the abstract `ConnectionHandler` wrapping a `vscode-languageclient` `LanguageClient`. Two concrete subclasses choose the transport based on `puppet.editorService.protocol`:
62+
63+
- [src/handlers/stdio.ts](src/handlers/stdio.ts)`StdioConnectionHandler` (default): spawns the bundled `puppet-languageserver` Ruby process over stdio.
64+
- [src/handlers/tcp.ts](src/handlers/tcp.ts)`TcpConnectionHandler`: connects to a local or remote editor services over TCP.
65+
66+
The handler starts the client, listens for telemetry, and polls `PuppetVersionRequest` to drive the status bar ("Loading facts/functions/types/classes…"). Custom LSP message types are in [src/messages.ts](src/messages.ts).
67+
68+
### Views & other layers
69+
70+
- [src/views/](src/views/)`TreeDataProvider`s for the Puppet activity-bar toolbar (Facts, Puppetfile). Registered at the end of `activate()`.
71+
- [src/logging/](src/logging/)`ILogger` implementations (output channel, file, stdout, null).
72+
- [src/telemetry.ts](src/telemetry.ts) — Application Insights reporter (`reporter`); only command names/config are collected, never file contents.
73+
- [src/forge.ts](src/forge.ts) — Puppet Forge API calls (e.g. latest PDK version check, module hover info) via axios.
74+
75+
### contributes (package.json)
76+
77+
Commands, menus, language definitions (`puppet`, `puppetfile`), grammars, snippets, the debugger type, the activity-bar view container, and all `puppet.*` configuration settings are declared in the `contributes` block of [package.json](package.json). When adding a command, register it in both `contributes.commands` and the relevant menu group, and wire its handler in a feature.
78+
79+
## Conventions
80+
81+
- TypeScript is compiled non-strict (`strict: false` in [tsconfig.json](tsconfig.json)); target es6/commonjs, output to `out/`.
82+
- ESLint uses `@typescript-eslint` with `prettier`; run `npm run fix` before committing.
83+
- Commit messages: first line ≤50 chars, imperative mood, prefixed with the GitHub issue as `(GH-XXXX) message` (see [CONTRIBUTING.md](CONTRIBUTING.md)). Branch naming: `GH-1234-short_description`.
84+
- The two language IDs (`puppet`, `puppetfile`) and the debugger type (`Puppet`) are hardcoded constants in [src/extension.ts](src/extension.ts) and must not change.
85+
86+
## Hard Constraints
87+
88+
- At the start of a coding session, review the repository structure and any relevant README or documentation files to understand the area you are working in.
89+
- Always read the files relevant to the task before suggesting or making a change.
90+
- Never merge a pull request.
91+
- Never work directly on the `main` or `master` branch.
92+
- Never push a branch without explicit instruction.
93+
- Never delete a file without permission — this applies even after a blanket "yes to all".
94+
- Never output, log, save, or hardcode security-sensitive values — this includes passwords, tokens, API keys, private keys, secrets, and credentials of any kind. Do not write them to files, include them in commit messages, or print them in responses.

0 commit comments

Comments
 (0)