From cb2d0fa836c0e573002c19e54910f99f93093d01 Mon Sep 17 00:00:00 2001 From: ftchvs Date: Tue, 26 May 2026 11:59:16 -0700 Subject: [PATCH] Refresh public workflow inventory --- CHANGELOG.md | 4 +++ README.md | 23 +++++++++++++ cli/aw.ts | 85 ++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 21 ++++++++++++ tests/aw.test.ts | 12 +++++++ 5 files changed, 145 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fab3677..000094f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/README.md b/README.md index 05f0476..593547a 100644 --- a/README.md +++ b/README.md @@ -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 @@ -66,12 +67,34 @@ aw validate aw check [workflow...] aw check-skills [skill...] aw publication-scan [--list] [file...] +aw inventory aw runbook aw audit aw new workflow aw new skill ``` +## 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 diff --git a/cli/aw.ts b/cli/aw.ts index 56e8e97..ba4a98b 100755 --- a/cli/aw.ts +++ b/cli/aw.ts @@ -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)); @@ -229,6 +234,7 @@ Usage: aw check [workflow...] aw check-skills [skill...] aw publication-scan [--list] [file...] + aw inventory aw runbook aw audit aw new workflow @@ -362,6 +368,59 @@ function printPublicationCoverage(paths: string[]): void { console.log(`listed ${paths.length} publication file(s)`); } +async function printInventory(): Promise { + 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} |`, + ); + } + + 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[] = []; @@ -423,6 +482,17 @@ async function loadSkill(path: string): Promise { return parseSkill(text, path); } +async function findExampleReadmes(): Promise { + 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)); @@ -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 { + 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; @@ -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, "\\$&"); } diff --git a/package.json b/package.json index 03dd47f..30d4e8c 100644 --- a/package.json +++ b/package.json @@ -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" }, diff --git a/tests/aw.test.ts b/tests/aw.test.ts index c15777e..5ec594e 100644 --- a/tests/aw.test.ts +++ b/tests/aw.test.ts @@ -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"]);