Skip to content
Draft
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
21 changes: 21 additions & 0 deletions .github/scripts/bundle-install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/usr/bin/env bash
# Wrapper script for bundle install
# Works around git bare repository issues when invoked from certain environments
# (VS Code/Copilot sets GIT_CONFIG_* to enforce safe.bareRepository=explicit)

set -euo pipefail

cd "$(dirname "$0")/../.."

# Clear VS Code's git config overrides that break bundler's bare repo clones
unset GIT_CONFIG_PARAMETERS

if [[ -n "${GIT_CONFIG_COUNT:-}" ]]; then
for ((i=0; i< GIT_CONFIG_COUNT; i++)); do
unset "GIT_CONFIG_KEY_${i}" "GIT_CONFIG_VALUE_${i}"
done
unset GIT_CONFIG_COUNT
fi
echo "==> Running bundle install..."
bundle install "$@"
echo "==> bundle install completed successfully"
69 changes: 69 additions & 0 deletions .github/skills/search-context/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
---
name: search-context
description: Search the shared context system for information. Use this whenever the AI or human needs contextual information about the products.
---

## Initialization Required

If `context/shared/map.md` does not exist, run /start-developement.

## Load Context Map

Load the context map. Understand the context types. Understand the levels of detail.

## Figure Out How to Walk the Levels Of Specificity

Ideally, the first time you search, you will discover how to search by learning the answers to these questions.

### Need to know repo

Determine which repository you are working in. This can usually be determined from git remote -v.

### Need to know if repo uses local context or shared context

Check for `context/shared/by-repo/ORG/REPO`. If so, anticipate searching per-repo context there.
If not, anticipate searching per-repo context at `context/local`.

### Need to know product

Determine product info from the per-repo `background/product-info.md` file. This determines `context/shared/by-product/PRODUCT/` search entries. There may be zero or multiple product associations.

### Need to know division

Determine which division produces the product from the per-repo `background/product-info.md` file. This determines `context/shared/by-division/DIVISION/` search entries. There may be zero or multiple division associations.

### Need to know business unit

Determine which business unit produces the product from the per-repo `background/product-info.md` file. This determines `context/shared/by-business-unit/UNIT/` search entries. There may be zero or multiple unit associations.

## Determine type of query

Decide if you are looking for designs, background, specifications, or what, based on the context types listed in the Context Map.

## Construct queries

You don't have to use grep, but these are examples.

```bash
grep -r context/shared/global/progress/background/**/*.md 'string'
grep -r context/shared/by-business-unit/infra/background/**/*.md 'string'
grep -r context/shared/by-division/chef/background/**/*.md 'string'
grep -r context/shared/by-product/chef-infra-client/background/**/*.md 'string'
grep -r context/shared/by-repo/chef/chef/background/**/*.md 'string'
```

```bash
grep -r context/shared/global/progress/standards/**/*.md 'string'
grep -r context/shared/by-business-unit/infra/standards/**/*.md 'string'
grep -r context/shared/by-division/next/standards/**/*.md 'string'
grep -r context/shared/by-product/alsi/standards/**/*.md 'string'
grep -r context/local/standards/**/*.md 'string'
```

## Reconcile Results

You will likely have multiple results. Merge the results and reconcile contradictions as follows:

1. Policy specifications higher in the tree are more influential. So a division-level standard should generally apply more than a product-level standard.
2. Technical specifications lower in the tree override specs context higher in the tree. So a technical specification to use a particular driver api might be needed for a good reason (which must be justified) and this override a higher-level mandate.
3. Any confusion or unresolved issues should be brough to the user's attention for a decision.
74 changes: 74 additions & 0 deletions .github/skills/start-development/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
---
name: start-development
description: Configure the repo for AI development. Do this before doing any work in the repo.
---

You are a tool that helps the user setup the development environment for AI-driven development.

You will do several tasks to set the user up.

First, determine if the user is running Windows, MacOS, or Linux. Use that information to decide what scripts to run.

## Load env file if present

Read eny env vars from etc/env.sh if present or etc/env.default.sh if not. You should source this in any shell you run.

## Setup gh

### Install gh

Install the gh GitHub CLI tool if it is not already installed.

### Ensure gh is authenticated

Make sure `gh auth status` works, and run `gh auth login` if not.

## Clone the shared-context repo

### Determine the location of the shared-context repo

The shared context repo location is at $PROGRESS_SHARED_CONTEXT_REPO which looks like org/repo@branch (branch defaults to main).

If no value is present, this defaults to `chef/shared-context@main`

### Clone, Re-Remote, Or Pull

If there are local changes, warn and do nothing.

If context/shared does not exist, clone the repo into it.

If it does exist confirm it is on the right remote and switch.

If it does exist confirm it is on the git branch and switch.

Pull.

## Ensure the list of reference repos is checked out

Look for the file `etc/reference-repo-list.txt`. Re-read the repo list each time you run — it may have changed. It is a list of GitHub repos to clone. Some of them may be private or internal; you may not have access. The list may include branch specifications like @branch.

Try to clone each one into `context/reference-repos`. If it has already been cloned, pull it. If a branch has been specified, make sure you are on that branch. If it has local changes, inform the user and do nothing.

Each time you run, check the repo status again. Do not remove repos, only add them.

## Ensure the Atlassian MCP server is running

Check for `.vscode/mcp.json` and look for the atlassian entry. If it is not running or has errored, ask the user to restart it.

## Ensure the user has rbenv installed and configured

Check if `rbenv` is installed by running `rbenv --version`. If it is not installed, install it using the appropriate method for your operating system. On MacOS, you can use `brew install rbenv`. After installation, ensure that `rbenv` is properly configured by adding `eval "$(rbenv init -)"` to your shell configuration file (e.g., `.bashrc`, `.zshrc`).

## Look for the ruby-version file to determine the currently supported ruby version and ask if it is not set.

Look for the file `.ruby-version` in the root of the repo. It should have a number like 3.4.8 or similar. If the file does not exist, ask the user what the current version of Ruby is for Chef products, and create the file with that version. Default to 3.4.8 if the user does not know.

## Ensure the user has the current ruby installed

Use `rbenv version` to check the currently installed Ruby version. If it does not match the version specified in `.ruby-version`, install the correct version using `rbenv install <version>`. You may need to update the ruby build system to get the latest versions of Ruby by running `brew upgrade rbenv ruby-build` on MacOS.

## Run bundle install using script

Run `bash .github/scripts/bundle-install.sh`

**Note:** Use the wrapper script instead of `bundle install` directly — it clears VS Code/Copilot git environment variables that break bundler's bare repository clones.
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,9 @@ terraform.tfstate.backup
.bundle
.gems
coverage/
Berksfile.lock
Berksfile.lock
etc/env.sh
context/reference-repos/**
context/shared
tmp/
.ruby-version
8 changes: 8 additions & 0 deletions .vscode/mcp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"servers": {
"atlassian-mcp-server": {
"url": "https://mcp.atlassian.com/v1/sse",
"type": "http"
}
}
}
Binary file added context/local/design/img/archetypes.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added context/local/design/img/architecture.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added context/local/design/img/class-diagram.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
71 changes: 71 additions & 0 deletions context/local/design/img/src/archetypes.puml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
@startuml archetypes
title Train Plugin V1 - Real-World Connection Archetypes

skinparam shadowing false
skinparam defaultFontName Helvetica
skinparam classAttributeIconSize 0

legend top
Green = implemented Red = deliberately omitted ("cheat")
All four are Train transports; three ship as train- gems, gcp ships in train core.
endlegend

class "train-winrm\n(remote-protocol / shell)" as WinRM {
+run_command_via_connection <color:green>YES</color>
+file_via_connection -> Remote::Windows <color:green>YES</color>
+upload/download (WinRM::FS) <color:green>YES</color>
+wait_until_ready / login_command <color:green>YES</color>
+connection(state,&block) reuse <color:green>YES</color>
+validate_options override <color:green>YES</color>
+lazy gem activation <color:green>YES</color>
+socks monkeypatch <color:green>YES</color>
--
family: (OS via Remote::Windows)
}

class "train-aws\n(API-only)" as Aws {
+run_command_via_connection <color:red>NO</color>
+file_via_connection <color:red>NO</color>
+aws_client / aws_resource <color:green>custom</color>
+enable_cache :api_call (forced) <color:green>YES</color>
+URI+ENV option mapping <color:green>YES</color>
+unique_identifier (STS) <color:green>YES</color>
--
platform: force_platform!("aws")
family: cloud
}

class "train-kubernetes\n(hybrid API + shell)" as K8s {
+run_command_via_connection -> kubectl exec <color:green>YES</color>
+file_via_connection -> custom File::Linux <color:green>YES</color>
+K8s::Client (k8s-ruby) API <color:green>YES</color>
+per-call opts (pod/ns/container) <color:green>YES</color>
--
platform: force_platform!("k8s")
family: cloud
}

class "gcp\n(API-only, in train core)" as Gcp {
+run_command_via_connection <color:red>NO</color>
+file_via_connection <color:red>NO</color>
+gcp_client(klass) / gcp_*_client <color:green>custom</color>
+enable_cache :api_call (forced) <color:green>YES</color>
+ENV-lazy option defaults <color:green>YES</color>
+unique_identifier (auth id) <color:green>YES</color>
--
platform: force_platform!("gcp")
family: cloud
}

WinRM -[hidden]right- Aws
Aws -[hidden]right- K8s
K8s -[hidden]right- Gcp

note bottom of Gcp
Not a separate gem: lib/train/transports/gcp.rb,
class Gcp < Train.plugin(1), name "gcp".
Sibling of the in-core azure transport; the closest
structural twin of train-aws. There is no train-gcp gem.
end note

@enduml
47 changes: 47 additions & 0 deletions context/local/design/img/src/architecture.puml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
@startuml architecture
title Train Plugin V1 - Component Architecture

skinparam componentStyle rectangle
skinparam shadowing false
skinparam defaultFontName Helvetica

actor "Consumer\n(InSpec / Chef Workstation / your app)" as App

package "Train core (the 'train' gem)" {
[Train.create / Train.options] as Facade
[load_transport\n(registry -> core -> gem)] as Loader
[Train::Plugins.registry\n(name -> Transport class)] as Registry
[Train::Options\n(option DSL, merge/validate)] as Options
[Train::Platforms::Detect\n(Scanner, families)] as Detect
[Train::File::*\n(Local/Remote file impls)] as Files
[Train::Plugins::Transport\n(= Train.plugin(1))] as BaseTransport
[BaseConnection\n(run_command/file/upload/cache)] as BaseConn
}

package "Plugin gem 'train-<name>'" {
[Entry point\nlib/train-<name>.rb] as Entry
[Transport\n< Train.plugin(1)] as PTransport
[Connection\n< BaseConnection] as PConn
[Platform mixin\n(force_platform!)] as PPlat
}

cloud "Target\n(host / API / cluster)" as Target

App --> Facade : create(name, opts)\noptions(name)
Facade --> Loader : resolve name
Loader ..> Registry : lookup
Loader ..> Entry : require "train-<name>"\n(on miss)
Entry --> PTransport : loads
Entry --> PConn : loads
Entry --> PPlat : loads
PTransport --|> BaseTransport
PConn --|> BaseConn
PTransport ..> Registry : name "<name>" registers self
PTransport --> PConn : connection() builds
PConn ..> PPlat : include (platform detection)
PConn ..> Files : wraps file objects
PConn ..> Detect : platform() / force_platform!
BaseTransport ..> Options : option / default_options
PConn --> Target : commands / files / API calls

@enduml
85 changes: 85 additions & 0 deletions context/local/design/img/src/class-diagram.puml
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
@startuml class-diagram
title Train Plugin V1 - Class Model

skinparam shadowing false
skinparam defaultFontName Helvetica
skinparam classAttributeIconSize 0

class "Train::Plugins" as Plugins {
+registry : Hash<String, Class>
}

class "Train::Plugins::Transport" as Transport {
{static} +name(name)
+initialize(options={})
+connection(_opts=nil) <<abstract>>
-logger
}
note right of Transport
Returned by Train.plugin(1).
Includes Train::Options and
Train::Extras.
end note

class "Train::Options" as Options <<module>> {
+option(name, conf=nil, &blk)
+default_options()
+include_options(other)
+merge_options(base, opts)
+validate_options(opts)
}

class "BaseConnection" as BaseConn {
+initialize(options=nil)
+run_command(cmd, opts={}, &h)
+file(path, *args)
+upload(locals, remote)
+download(remotes, local)
+platform() <<alias os>>
+login_command()
+wait_until_ready()
+close()
+enable_cache(type) / disable_cache(type)
+cached_client(type, key)
+force_platform!(name, details=nil)
+to_json / load_json(j)
--
#run_command_via_connection(cmd, opts, &h) <<implement>>
#file_via_connection(path, *args) <<implement>>
}

class "CommandResult" as CmdResult <<Struct>> {
+stdout
+stderr
+exit_status
}

class "Train::File" as TFile {
+DATA_FIELDS
+exist? mode owner group content mtime size ...
}

' --- Plugin-provided classes ---
class "TrainPlugins::X::Transport" as XTransport {
+name "x"
+connection(_=nil)
}
class "TrainPlugins::X::Connection" as XConn {
#run_command_via_connection(...)
#file_via_connection(...)
}
class "TrainPlugins::X::Platform" as XPlat <<module>> {
+platform()
}

Plugins ..> Transport : registers
Transport ..|> Options : includes
Transport +-- BaseConn : nested
BaseConn ..> CmdResult : returns
BaseConn ..> TFile : returns
XTransport --|> Transport
XConn --|> BaseConn
XConn ..|> XPlat : include
XTransport --> XConn : builds

@enduml
Loading
Loading