feat: scaffold us-cpa skill

This commit is contained in:
Stefano Fiorini
2026-03-15 00:45:06 -05:00
parent 59dbaf8a6c
commit 291b729894
11 changed files with 342 additions and 0 deletions

56
docs/us-cpa.md Normal file
View File

@@ -0,0 +1,56 @@
# us-cpa
`us-cpa` is a Python CLI plus OpenClaw skill wrapper for U.S. federal individual tax work.
## Current Milestone
Milestone 1 provides the initial package, CLI surface, skill wrapper, and test harness. Tax logic, IRS corpus download, case workflows, rendering, and review logic are not implemented yet.
## CLI Surface
```bash
skills/us-cpa/scripts/us-cpa question --question "What is the standard deduction?" --tax-year 2025
skills/us-cpa/scripts/us-cpa prepare --tax-year 2025 --case-dir ~/tax-cases/2025-jane-doe
skills/us-cpa/scripts/us-cpa review --tax-year 2025 --case-dir ~/tax-cases/2025-jane-doe
skills/us-cpa/scripts/us-cpa fetch-year --tax-year 2025
skills/us-cpa/scripts/us-cpa extract-docs --tax-year 2025 --case-dir ~/tax-cases/2025-jane-doe
skills/us-cpa/scripts/us-cpa render-forms --tax-year 2025 --case-dir ~/tax-cases/2025-jane-doe
skills/us-cpa/scripts/us-cpa export-efile-ready --tax-year 2025 --case-dir ~/tax-cases/2025-jane-doe
```
## Interaction Model
- `question`
- stateless by default
- optional case context
- `prepare`
- requires a case directory
- if none exists, OpenClaw should ask whether to create one and where
- `review`
- requires a case directory
- can operate on an existing or newly-created review case
## Planned Case Layout
```text
<case-dir>/
input/
extracted/
return/
output/
reports/
issues/
sources/
```
## Output Contract
- JSON by default
- markdown available with `--format markdown`
- current milestone responses are scaffold payloads with `status: "not_implemented"`
## Scope Rules
- U.S. federal individual returns only in v1
- official IRS artifacts are the target output for compiled forms
- conflicting facts must stop the workflow for user resolution

52
skills/us-cpa/SKILL.md Normal file
View File

@@ -0,0 +1,52 @@
---
name: us-cpa
description: Use when answering U.S. federal individual tax questions, preparing a federal Form 1040 return package, or reviewing a draft/completed federal individual return.
---
# US CPA
`us-cpa` is a Python-first federal individual tax workflow skill. The CLI is the canonical engine. Use the skill to classify the request, gather missing inputs, and invoke the CLI.
## Modes
- `question`
- one-off federal tax question
- case folder optional
- `prepare`
- new or existing return-preparation case
- case folder required
- `review`
- new or existing return-review case
- case folder required
## Agent Workflow
1. Determine whether the request is:
- question-only
- a new preparation/review case
- work on an existing case
2. If the request is `prepare` or `review` and no case folder is supplied:
- ask whether to create a new case
- ask where to store it
3. Use the bundled CLI:
```bash
skills/us-cpa/scripts/us-cpa question --question "What is the standard deduction?" --tax-year 2025
skills/us-cpa/scripts/us-cpa prepare --tax-year 2025 --case-dir ~/tax-cases/2025-jane-doe
skills/us-cpa/scripts/us-cpa review --tax-year 2025 --case-dir ~/tax-cases/2025-jane-doe
```
## Rules
- federal individual returns only in v1
- IRS materials first; escalate to primary law only when needed
- stop on conflicting facts and ask the user to resolve the issue before continuing
- official IRS PDFs are the target compiled-form artifacts
- overlay-rendered forms must be flagged for human review
## Output
- JSON by default
- markdown output available with `--format markdown`
For operator details, limitations, and the planned case structure, see `docs/us-cpa.md`.

View File

@@ -0,0 +1,19 @@
[build-system]
requires = ["setuptools>=68"]
build-backend = "setuptools.build_meta"
[project]
name = "us-cpa"
version = "0.1.0"
description = "US federal individual tax workflow CLI for questions, preparation, and review."
requires-python = ">=3.9"
dependencies = []
[project.scripts]
us-cpa = "us_cpa.cli:main"
[tool.setuptools]
package-dir = {"" = "src"}
[tool.setuptools.packages.find]
where = ["src"]

8
skills/us-cpa/scripts/us-cpa Executable file
View File

@@ -0,0 +1,8 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SKILL_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
export PYTHONPATH="${SKILL_DIR}/src${PYTHONPATH:+:${PYTHONPATH}}"
exec python3 -m us_cpa.cli "$@"

View File

@@ -0,0 +1,2 @@
"""us-cpa package."""

View File

@@ -0,0 +1,130 @@
from __future__ import annotations
import argparse
import json
import sys
from pathlib import Path
from typing import Any
COMMANDS = (
"question",
"prepare",
"review",
"fetch-year",
"extract-docs",
"render-forms",
"export-efile-ready",
)
def _add_common_arguments(
parser: argparse.ArgumentParser, *, include_tax_year: bool = True
) -> None:
if include_tax_year:
parser.add_argument("--tax-year", type=int, default=None)
parser.add_argument("--case-dir", default=None)
parser.add_argument("--format", choices=("json", "markdown"), default="json")
def _emit(payload: dict[str, Any], output_format: str) -> int:
if output_format == "markdown":
lines = [f"# {payload['command']}"]
for key, value in payload.items():
if key == "command":
continue
lines.append(f"- **{key}**: {value}")
print("\n".join(lines))
else:
print(json.dumps(payload, indent=2))
return 0
def _require_case_dir(args: argparse.Namespace) -> Path:
if not args.case_dir:
raise SystemExit("A case directory is required for this command.")
return Path(args.case_dir).expanduser().resolve()
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
prog="us-cpa",
description="US federal individual tax workflow CLI.",
)
subparsers = parser.add_subparsers(dest="command", required=True)
question = subparsers.add_parser("question", help="Answer a tax question.")
_add_common_arguments(question)
question.add_argument("--question", required=True)
prepare = subparsers.add_parser("prepare", help="Prepare a return case.")
_add_common_arguments(prepare)
review = subparsers.add_parser("review", help="Review a return case.")
_add_common_arguments(review)
fetch_year = subparsers.add_parser(
"fetch-year", help="Fetch tax-year forms and instructions."
)
_add_common_arguments(fetch_year, include_tax_year=False)
fetch_year.add_argument("--tax-year", type=int, required=True)
extract_docs = subparsers.add_parser(
"extract-docs", help="Extract facts from case documents."
)
_add_common_arguments(extract_docs)
render_forms = subparsers.add_parser(
"render-forms", help="Render compiled IRS forms."
)
_add_common_arguments(render_forms)
export_efile = subparsers.add_parser(
"export-efile-ready", help="Export an e-file-ready payload."
)
_add_common_arguments(export_efile)
return parser
def main(argv: list[str] | None = None) -> int:
parser = build_parser()
args = parser.parse_args(argv)
if args.command == "question":
payload = {
"command": "question",
"format": args.format,
"taxYear": args.tax_year,
"caseDir": args.case_dir,
"question": args.question,
"status": "not_implemented",
}
return _emit(payload, args.format)
if args.command in {"prepare", "review", "extract-docs", "render-forms", "export-efile-ready"}:
case_dir = _require_case_dir(args)
payload = {
"command": args.command,
"format": args.format,
"taxYear": args.tax_year,
"caseDir": str(case_dir),
"status": "not_implemented",
}
return _emit(payload, args.format)
if args.command == "fetch-year":
payload = {
"command": "fetch-year",
"format": args.format,
"taxYear": args.tax_year,
"status": "not_implemented",
}
return _emit(payload, args.format)
parser.error(f"Unsupported command: {args.command}")
return 2
if __name__ == "__main__":
sys.exit(main())

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,71 @@
from __future__ import annotations
import json
import os
import subprocess
import sys
import unittest
from pathlib import Path
SKILL_DIR = Path(__file__).resolve().parents[1]
SRC_DIR = SKILL_DIR / "src"
class UsCpaCliSmokeTests(unittest.TestCase):
def test_skill_scaffold_files_exist(self) -> None:
self.assertTrue((SKILL_DIR / "SKILL.md").exists())
self.assertTrue((SKILL_DIR / "pyproject.toml").exists())
self.assertTrue((SKILL_DIR / "scripts" / "us-cpa").exists())
self.assertTrue(
(SKILL_DIR.parent.parent / "docs" / "us-cpa.md").exists()
)
def test_fixture_directories_exist(self) -> None:
fixtures_dir = SKILL_DIR / "tests" / "fixtures"
for name in ("irs", "facts", "documents", "returns"):
self.assertTrue((fixtures_dir / name).exists())
def run_cli(self, *args: str) -> subprocess.CompletedProcess[str]:
env = os.environ.copy()
env["PYTHONPATH"] = str(SRC_DIR)
return subprocess.run(
[sys.executable, "-m", "us_cpa.cli", *args],
text=True,
capture_output=True,
env=env,
)
def test_help_lists_all_commands(self) -> None:
result = self.run_cli("--help")
self.assertEqual(result.returncode, 0, result.stderr)
for command in (
"question",
"prepare",
"review",
"fetch-year",
"extract-docs",
"render-forms",
"export-efile-ready",
):
self.assertIn(command, result.stdout)
def test_question_command_emits_json_by_default(self) -> None:
result = self.run_cli("question", "--question", "What is the standard deduction?")
self.assertEqual(result.returncode, 0, result.stderr)
payload = json.loads(result.stdout)
self.assertEqual(payload["command"], "question")
self.assertEqual(payload["format"], "json")
self.assertEqual(payload["question"], "What is the standard deduction?")
def test_prepare_requires_case_dir(self) -> None:
result = self.run_cli("prepare", "--tax-year", "2025")
self.assertNotEqual(result.returncode, 0)
self.assertIn("case directory", result.stderr.lower())
if __name__ == "__main__":
unittest.main()