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
112 changes: 112 additions & 0 deletions aws_doc_sdk_examples_tools/fs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
from abc import ABC, abstractmethod
from dataclasses import dataclass
from fnmatch import fnmatch
from os import listdir
from pathlib import Path
from stat import S_ISREG
from typing import Dict, Generator, List


@dataclass(frozen=True)
class Stat:
path: Path
exists: bool
is_file: bool

@property
def is_dir(self):
return self.exists and not self.is_file


class Fs(ABC):
@abstractmethod
def glob(self, path: Path, glob: str) -> Generator[Path, None, None]:
pass

@abstractmethod
def read(self, path: Path) -> str:
pass

@abstractmethod
def write(self, path: Path, content: str):
pass

@abstractmethod
def stat(self, path: Path) -> Stat:
pass

@abstractmethod
def mkdir(self, path: Path):
pass

@abstractmethod
def list(self, path: Path) -> List[Path]:
pass


class PathFs(Fs):
def glob(self, path: Path, glob: str) -> Generator[Path, None, None]:
return path.glob(glob)

def read(self, path: Path) -> str:
with path.open("r") as file:
return file.read()

def write(self, path: Path, content: str):
with path.open("w") as file:
file.write(content)

def stat(self, path: Path) -> Stat:
if path.exists():
stat = path.stat()
return Stat(path, True, S_ISREG(stat.st_mode))
else:
return Stat(path, False, False)

def mkdir(self, path: Path):
path.mkdir(parents=True, exist_ok=True)

def list(self, path: Path) -> List[Path]:
if self.stat(path).is_file:
return []
return [path / name for name in listdir(path)]


class RecordFs(Fs):
def __init__(self, fs: Dict[Path, str]):
self.fs = fs

def glob(self, path: Path, glob: str) -> Generator[Path, None, None]:
path_s = str(path)
for key in self.fs.keys():
key_s = str(key)
if key_s.startswith(path_s):
if fnmatch(key_s, glob):
yield key

def read(self, path: Path) -> str:
return self.fs[path]

def write(self, path: Path, content: str):
base = str(path.parent)
assert any(
[str(key).startswith(base) for key in self.fs]
), "No parent folder, this will probably fail without a call to mkdir in a real file system!"
self.fs[path] = content

def stat(self, path: Path):
if path in self.fs:
return Stat(path, True, True)
for item in self.fs.keys():
if str(item).startswith(str(path)):
return Stat(path, True, False)
return Stat(path, False, False)

def mkdir(self, path: Path):
self.fs.setdefault(path, "")

def list(self, path: Path) -> List[Path]:
return [item for item in self.fs.keys() if item.parent == path]


fs = PathFs()
78 changes: 78 additions & 0 deletions aws_doc_sdk_examples_tools/lliam/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Ailly Prompt Workflow

This project automates the process of generating, running, parsing, and applying [Ailly](https://www.npmjs.com/package/@ailly/cli) prompt outputs to an AWS DocGen project. It combines all steps into one streamlined command using a single Python script.

---

## 📦 Overview

This tool:
1. **Generates** Ailly prompts from DocGen snippets.
2. **Runs** Ailly CLI to get enhanced metadata.
3. **Parses** Ailly responses into structured JSON.
4. **Updates** your DocGen examples with the new metadata.

All of this is done with one command.

---

## ✅ Prerequisites

- Python 3.8+
- Node.js and npm (for `npx`)
- A DocGen project directory

---

## 🚀 Usage

From your project root, run:

```bash
python -m aws_doc_sdk_examples_tools.agent.bin.main \
/path/to/your/docgen/project \
--system-prompts path/to/system_prompt.txt
```

### 🔧 Arguments

- `iam_tributary_root`: Path to the root directory of your IAM policy tributary
- `--system-prompts`: List of system prompt files or strings to include in the Ailly configuration
- `--skip-generation`: Skip the prompt generation and Ailly execution steps (useful for reprocessing existing outputs)

Run `python -m aws_doc_sdk_examples_tools.agent.bin.main update --help` for more info.

---

## 🗂 What This Does

Under the hood, this script:

1. Creates a directory `.ailly_iam_policy` containing:
- One Markdown file per snippet.
- A `.aillyrc` configuration file.

2. Runs `npx @ailly/cli` to generate `.ailly.md` outputs.

3. Parses the Ailly `.ailly.md` files into a single `iam_updates.json` file.

4. Updates each matching `Example` in the DocGen instance with:
- `title`
- `title_abbrev`
- `synopsis`

---

## 💡 Example

```bash
python -m aws_doc_sdk_examples_tools.agent.bin.main \
~/projects/AWSIAMPolicyExampleReservoir \
--system-prompts prompts/system_prompt.txt
```

This will:
- Write prompts and config to `.ailly_iam_policy/`
- Run Ailly and capture results
- Parse and save output as `.ailly_iam_policy/iam_updates.json`
- Apply updates to your DocGen examples
26 changes: 26 additions & 0 deletions aws_doc_sdk_examples_tools/lliam/domain/commands.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from dataclasses import dataclass
from pathlib import Path
from typing import List


class Command:
pass


@dataclass
class CreatePrompts(Command):
doc_gen_root: str
system_prompts: List[str]
out_dir: str


@dataclass
class RunAilly(Command):
batches: List[str]


@dataclass
class UpdateReservoir(Command):
root: Path
batches: List[str]
packages: List[str]
15 changes: 15 additions & 0 deletions aws_doc_sdk_examples_tools/lliam/domain/model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from typing import List

from aws_doc_sdk_examples_tools.doc_gen import Example, Snippet


class Prompt:
def __init__(self, id: str, content: str):
self.id = id
self.content = content


class Policies:
def __init__(self, examples: List[Example], snippets: List[Snippet]):
self.examples = examples
self.snippets = snippets
28 changes: 28 additions & 0 deletions aws_doc_sdk_examples_tools/lliam/domain/operations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import yaml
from typing import List

from aws_doc_sdk_examples_tools.lliam.domain.model import Prompt


def build_ailly_config(system_prompts: List[Prompt]) -> Prompt:
"""Create the .aillyrc configuration file."""
fence = "---"
options = {
"isolated": "true",
"overwrite": "true",
# MCP assistance did not produce noticeably different results, but it was
# slowing things down by 10x. Disabled for now.
# "mcp": {
# "awslabs.aws-documentation-mcp-server": {
# "type": "stdio",
# "command": "uvx",
# "args": ["awslabs.aws-documentation-mcp-server@latest"],
# }
# },
}
options_block = yaml.dump(options).strip()
prompt_strs = [p.content for p in system_prompts]
prompts_block = "\n".join(prompt_strs)

content = f"{fence}\n{options_block}\n{fence}\n{prompts_block}"
return Prompt(".aillyrc", content)