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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@

### Added

- `aw inventory` to summarize public workflow, skill, and example assets.
- A five-minute README tour for validating and evaluating the repo quickly.
- Package metadata for repository links, issue links, keywords, and Bun engine
expectations.
- Public-safe growth marketing skill drafts for analytics consent audits,
Google Ads upload QA, ad preflight review, paid social launch gating,
technical SEO launch audits, product marketing context, growth loop
Expand Down
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ Run the CLI from the repo root:

```sh
bun run validate
bun cli/aw.ts inventory
bun cli/aw.ts check
bun cli/aw.ts check-skills
bun cli/aw.ts publication-scan
Expand All @@ -66,12 +67,34 @@ aw validate <workflow>
aw check [workflow...]
aw check-skills [skill...]
aw publication-scan [--list] [file...]
aw inventory
aw runbook <workflow>
aw audit <workflow>
aw new workflow <name>
aw new skill <name>
```

## Five-minute tour

If you are evaluating the repo, run these in order:

```sh
bun run validate
bun cli/aw.ts inventory
bun cli/aw.ts runbook workflows/repo-triage.workflow.yml
bun cli/aw.ts audit workflows/external-action-gate.workflow.yml
```

That gives you:

1. a full validation gate for tests, workflows, skills, and publication safety
2. an inventory of the workflow, skill, and example surface area
3. a rendered runbook for repo triage
4. an authority audit for external-write approval boundaries

Start with [Fictional product audit](examples/fictional-product-audit/README.md)
for the best end-to-end example of the operating loop.

## Who this is for

- founders, operators, chiefs of staff, and product leads adopting AI agents
Expand Down
85 changes: 85 additions & 0 deletions cli/aw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,11 @@ async function main() {
return;
}

if (command === "inventory") {
await printInventory();
return;
}

if (command === "runbook") {
const workflow = await checkedWorkflow(requiredArg(args[0], "workflow"));
console.log(renderRunbook(workflow));
Expand Down Expand Up @@ -229,6 +234,7 @@ Usage:
aw check [workflow...]
aw check-skills [skill...]
aw publication-scan [--list] [file...]
aw inventory
aw runbook <workflow>
aw audit <workflow>
aw new workflow <name>
Expand Down Expand Up @@ -362,6 +368,59 @@ function printPublicationCoverage(paths: string[]): void {
console.log(`listed ${paths.length} publication file(s)`);
}

async function printInventory(): Promise<void> {
const workflowPaths = await findWorkflowPaths();
const skillPaths = await findSkillPaths();
const examplePaths = await findExampleReadmes();

const workflows = await Promise.all(
workflowPaths.map(async (path) => ({ path, workflow: await loadWorkflow(path) })),
);
const skills = await Promise.all(
skillPaths.map(async (path) => ({ path, skill: await loadSkill(path) })),
);
const examples = await Promise.all(
examplePaths.map(async (path) => ({ path, title: await readMarkdownTitle(path) })),
);

console.log(`# Agentic Workflows Inventory

## Summary
- Workflows: ${workflows.length}
- Skills: ${skills.length}
- Examples: ${examples.length}

## Workflows
| File | Name | Risk | Authority |
| --- | --- | --- | --- |`);

for (const { path, workflow } of workflows) {
console.log(
`| ${path} | ${escapeTableCell(workflow.name)} | ${workflow.risk_level} | ${workflow.authority} |`,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Handle missing workflow names in inventory rows

aw inventory can crash instead of producing a report when any workflow file is parseable YAML but lacks name. In that case loadWorkflow returns an object with workflow.name === undefined, and this line passes it to escapeTableCell, which calls .replace on a non-string and throws (undefined is not an object). This makes the inventory command fail on malformed or in-progress workflow files rather than surfacing a validation-style error or a safe placeholder.

Useful? React with 👍 / 👎.

);
}

console.log(`
## Skills
| File | Name | Description |
| --- | --- | --- |`);

for (const { path, skill } of skills) {
console.log(
`| ${path} | ${escapeTableCell(skill.name ?? "")} | ${escapeTableCell(compact(skill.description ?? "", 96))} |`,
);
}

console.log(`
## Examples
| File | Title |
| --- | --- |`);

for (const { path, title } of examples) {
console.log(`| ${path} | ${escapeTableCell(title)} |`);
}
}

function validateWorkflow(workflow: Workflow): string[] {
const errors: string[] = [];

Expand Down Expand Up @@ -423,6 +482,17 @@ async function loadSkill(path: string): Promise<Skill> {
return parseSkill(text, path);
}

async function findExampleReadmes(): Promise<string[]> {
const glob = new Bun.Glob("examples/**/README.md");
const paths: string[] = [];

for await (const path of glob.scan(".")) {
if (path !== "examples/README.md") paths.push(path);
}

return paths.sort();
}

function validateSkill(skill: Skill, path: string): string[] {
const errors: string[] = [];
const directoryName = basename(dirname(path));
Expand Down Expand Up @@ -787,6 +857,12 @@ function normalizeSkillPath(path: string): string {
return path.endsWith("SKILL.md") ? path : `${path.replace(/\/$/, "")}/SKILL.md`;
}

async function readMarkdownTitle(path: string): Promise<string> {
const text = await Bun.file(path).text();
const title = text.match(/^#\s+(.+)$/m)?.[1]?.trim();
return title || basename(dirname(path));
}

function requiredArg(value: string | undefined, name: string): string {
if (!value) fail(`missing required argument: ${name}`);
return value;
Expand Down Expand Up @@ -816,6 +892,15 @@ function hasMarkdownSection(body: string, section: string): boolean {
return new RegExp(`^${escapeRegex(section)}\\s*$`, "m").test(body);
}

function compact(value: string, maxLength: number): string {
if (value.length <= maxLength) return value;
return `${value.slice(0, maxLength - 3).trimEnd()}...`;
}

function escapeTableCell(value: string): string {
return value.replace(/\|/g, "\\|").replace(/\r?\n/g, " ");
}

function escapeRegex(value: string): string {
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
Expand Down
21 changes: 21 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,30 @@
{
"name": "agentic-workflows",
"version": "0.2.0",
"description": "Repo-native operating files, workflow schemas, and validation tools for controlled AI work.",
"private": false,
"license": "CC-BY-4.0",
"type": "module",
"repository": {
"type": "git",
"url": "git+https://github.com/ftchvs/agentic-workflows.git"
},
"homepage": "https://github.com/ftchvs/agentic-workflows#readme",
"bugs": {
"url": "https://github.com/ftchvs/agentic-workflows/issues"
},
"keywords": [
"agentic-workflows",
"ai-agents",
"workflow-automation",
"human-in-the-loop",
"prompt-engineering",
"ai-governance",
"bun"
],
"engines": {
"bun": ">=1.2.0"
},
"bin": {
"aw": "./cli/aw.ts"
},
Expand Down
12 changes: 12 additions & 0 deletions tests/aw.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,18 @@ test("publication-scan validates public repo artifacts", async () => {
expect(result.stdout).toContain(" publication file(s)");
});

test("inventory summarizes public workflow assets", async () => {
const result = await runAw(["inventory"]);

expect(result.exitCode).toBe(0);
expect(result.stdout).toContain("# Agentic Workflows Inventory");
expect(result.stdout).toContain("- Workflows: 12");
expect(result.stdout).toContain("- Skills: 8");
expect(result.stdout).toContain("| workflows/repo-triage.workflow.yml | Repo Triage | read-only | read_only |");
expect(result.stdout).toContain("| skills/ad-preflight-review/SKILL.md | ad-preflight-review |");
expect(result.stdout).toContain("| examples/fictional-product-audit/README.md | Fictional case study: product audit to decision memo |");
});

test("publication-scan lists covered public repo artifacts", async () => {
const result = await runAw(["publication-scan", "--list"]);

Expand Down
Loading