docguard-cli 0.18.1 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/README.md +128 -34
  2. package/cli/commands/demo.mjs +241 -0
  3. package/cli/commands/guard.mjs +20 -2
  4. package/cli/commands/init.mjs +122 -0
  5. package/cli/docguard.mjs +125 -47
  6. package/cli/validators/canonical-sync.mjs +211 -0
  7. package/cli/validators/spec-kit.mjs +14 -0
  8. package/docs/quickstart.md +1 -1
  9. package/extensions/spec-kit-docguard/README.md +1 -1
  10. package/extensions/spec-kit-docguard/extension.yml +5 -5
  11. package/extensions/spec-kit-docguard/skills/docguard-fix/SKILL.md +2 -2
  12. package/extensions/spec-kit-docguard/skills/docguard-guard/SKILL.md +2 -2
  13. package/extensions/spec-kit-docguard/skills/docguard-review/SKILL.md +2 -2
  14. package/extensions/spec-kit-docguard/skills/docguard-score/SKILL.md +2 -2
  15. package/extensions/spec-kit-docguard/skills/docguard-sync/SKILL.md +1 -1
  16. package/package.json +1 -1
  17. package/templates/demo-fixture/.docguard.json +8 -0
  18. package/templates/demo-fixture/.env.example +5 -0
  19. package/templates/demo-fixture/AGENTS.md +14 -0
  20. package/templates/demo-fixture/CHANGELOG.md +13 -0
  21. package/templates/demo-fixture/DRIFT-LOG.md +3 -0
  22. package/templates/demo-fixture/README.md +17 -0
  23. package/templates/demo-fixture/docs-canonical/API-REFERENCE.md +36 -0
  24. package/templates/demo-fixture/docs-canonical/ARCHITECTURE.md +30 -0
  25. package/templates/demo-fixture/docs-canonical/DATA-MODEL.md +30 -0
  26. package/templates/demo-fixture/docs-canonical/ENVIRONMENT.md +20 -0
  27. package/templates/demo-fixture/docs-canonical/SECURITY.md +15 -0
  28. package/templates/demo-fixture/docs-canonical/TEST-SPEC.md +10 -0
  29. package/templates/demo-fixture/package.json +10 -0
  30. package/templates/demo-fixture/src/api.mjs +18 -0
  31. package/templates/demo-fixture/src/notifier.mjs +23 -0
  32. package/templates/demo-fixture/src/scheduler.mjs +8 -0
  33. package/templates/demo-fixture/src/worker.mjs +15 -0
@@ -0,0 +1,13 @@
1
+ # Changelog
2
+
3
+ > Note: This demo CHANGELOG is intentionally missing an `[Unreleased]` section
4
+ > so DocGuard's Changelog validator has something to flag.
5
+
6
+ ## [1.4.0] - 2026-04-12
7
+
8
+ - Add scheduled retry for failed charges
9
+ - Bump Stripe SDK to v15
10
+
11
+ ## [1.3.0] - 2026-03-28
12
+
13
+ - Initial public release
@@ -0,0 +1,3 @@
1
+ # DRIFT-LOG
2
+
3
+ > Track intentional deviations from `docs-canonical/`. Every `// DRIFT:` comment in code needs a row here.
@@ -0,0 +1,17 @@
1
+ # Acme Payments
2
+
3
+ A payments microservice. Handles charges, refunds, balance lookups.
4
+
5
+ ## Stack
6
+ - Node.js (ES modules)
7
+ - PostgreSQL
8
+ - Stripe for card processing
9
+
10
+ ## Quick start
11
+
12
+ ```bash
13
+ npm install
14
+ npm start
15
+ ```
16
+
17
+ See `docs-canonical/` for the system specs.
@@ -0,0 +1,36 @@
1
+ # API Reference
2
+
3
+ > All requests require `Authorization: Bearer <jwt>` unless noted.
4
+
5
+ ## Charges
6
+
7
+ ### POST /charge
8
+ Create a new charge.
9
+
10
+ **Request body**
11
+ ```json
12
+ { "amount_cents": 1000, "currency": "USD", "customer_id": "cus_..." }
13
+ ```
14
+
15
+ **Response** — `201 Created` with the charge object.
16
+
17
+ ### POST /refund
18
+ Refund a previous charge.
19
+
20
+ **Request body**
21
+ ```json
22
+ { "charge_id": "ch_...", "amount_cents": 1000 }
23
+ ```
24
+
25
+ ## Balance
26
+
27
+ ### GET /balance/:customer_id
28
+ Look up a customer's current balance.
29
+
30
+ **Response**
31
+ ```json
32
+ { "customer_id": "cus_...", "available_cents": 12345 }
33
+ ```
34
+
35
+ <!-- Demo drift: code also exposes POST /webhooks (Stripe callbacks) but it's
36
+ missing from this reference. DocGuard's API-Surface validator catches it. -->
@@ -0,0 +1,30 @@
1
+ # ARCHITECTURE — Acme Payments
2
+
3
+ > The system has **3 services**. (Demo drift: code actually has 4 — see `src/`.)
4
+
5
+ ## Components
6
+
7
+ ```
8
+ ┌───────────┐ queue ┌──────────┐ cron ┌────────────┐
9
+ │ API │ ────────> │ Worker │ <─────── │ Scheduler │
10
+ │ (HTTP) │ │ (jobs) │ │ (timers) │
11
+ └───────────┘ └──────────┘ └────────────┘
12
+ ```
13
+
14
+ ### API
15
+ Handles HTTP requests. Routes live in `src/api.mjs`.
16
+
17
+ ### Worker
18
+ Consumes the job queue. Long-running tasks (capture, refund settlement).
19
+
20
+ ### Scheduler
21
+ Cron-style triggers for retries and reconciliation.
22
+
23
+ ## Data flow
24
+ 1. Client POSTs to `/charge` → API validates → enqueues `process_charge` job
25
+ 2. Worker dequeues → calls Stripe → writes result to DB
26
+ 3. Scheduler reruns failed charges hourly
27
+
28
+ ## See also
29
+ - `DATA-MODEL.md` for the persistence layer
30
+ - `SECURITY.md` for auth + secrets handling
@@ -0,0 +1,30 @@
1
+ # Data Model
2
+
3
+ ## charges
4
+
5
+ | Column | Type | Notes |
6
+ |--------|------|-------|
7
+ | `id` | `uuid` (PK) | |
8
+ | `customer_id` | `text` | |
9
+ | `amount_cents` | `bigint` | |
10
+ | `currency` | `text` | ISO-4217 |
11
+ | `status` | `text` | `pending` / `succeeded` / `failed` |
12
+ | `stripe_id` | `text` | nullable |
13
+ | `created_at` | `timestamptz` | default now() |
14
+
15
+ ## refunds
16
+
17
+ | Column | Type |
18
+ |--------|------|
19
+ | `id` | `uuid` (PK) |
20
+ | `charge_id` | `uuid` (FK → charges) |
21
+ | `amount_cents` | `bigint` |
22
+ | `created_at` | `timestamptz` |
23
+
24
+ ## customers
25
+
26
+ | Column | Type |
27
+ |--------|------|
28
+ | `id` | `text` (PK, `cus_...`) |
29
+ | `email` | `text` |
30
+ | `created_at` | `timestamptz` |
@@ -0,0 +1,20 @@
1
+ # Environment
2
+
3
+ Required environment variables.
4
+
5
+ | Variable | Required | Description |
6
+ |----------|----------|-------------|
7
+ | `DATABASE_URL` | Yes | Postgres connection string |
8
+ | `STRIPE_SECRET_KEY` | Yes | Stripe API key (server-side) |
9
+ | `REDIS_URL` | Yes | Redis URL for the job queue |
10
+
11
+ <!-- Demo drift: REDIS_URL is documented here but missing from .env.example.
12
+ Also, JWT_SECRET is in .env.example + used in code, but not listed here.
13
+ DocGuard's Environment validator catches both. -->
14
+
15
+ ## Local development
16
+
17
+ ```bash
18
+ cp .env.example .env
19
+ # Fill in the values above
20
+ ```
@@ -0,0 +1,15 @@
1
+ # Security
2
+
3
+ ## Authentication
4
+ HTTP API requires `Authorization: Bearer <jwt>`. Tokens are signed with `JWT_SECRET`.
5
+
6
+ ## Secrets
7
+ - `STRIPE_SECRET_KEY` — never logged
8
+ - `JWT_SECRET` — rotated quarterly
9
+
10
+ ## Threat model
11
+ - Card data is never stored locally — Stripe-tokenized only
12
+ - `JWT_SECRET` rotation invalidates outstanding sessions (acceptable for an internal API)
13
+
14
+ ## Audit log
15
+ Every charge / refund writes to the `audit_events` table.
@@ -0,0 +1,10 @@
1
+ # Test Spec
2
+
3
+ ## Coverage rules
4
+ - Every route in `src/api.mjs` must have an integration test in `tests/api/`
5
+ - Every worker job must have a unit test in `tests/worker/`
6
+
7
+ ## Layers
8
+ - **Unit** — pure logic, no I/O
9
+ - **Integration** — hits a local Postgres + Stripe in test mode
10
+ - **E2E** — against a staging stack (rare; only for release candidates)
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "acme-payments",
3
+ "version": "1.4.0",
4
+ "description": "Demo project for DocGuard — a 4-service payments API with intentional doc drift.",
5
+ "private": true,
6
+ "type": "module",
7
+ "scripts": {
8
+ "start": "node src/api.mjs"
9
+ }
10
+ }
@@ -0,0 +1,18 @@
1
+ // API service — handles HTTP routes.
2
+ import { createServer } from 'node:http';
3
+
4
+ // Routes intentionally include /webhooks (not in API-REFERENCE.md — demo drift)
5
+ const ROUTES = {
6
+ 'POST /charge': createCharge,
7
+ 'POST /refund': createRefund,
8
+ 'GET /balance/:customer': getBalance,
9
+ 'POST /webhooks': handleStripeWebhook, // ← undocumented
10
+ };
11
+
12
+ async function createCharge(req) { /* ... */ }
13
+ async function createRefund(req) { /* ... */ }
14
+ async function getBalance(req) { /* ... */ }
15
+ async function handleStripeWebhook(req) { /* ... */ }
16
+
17
+ const PORT = process.env.PORT || 3000;
18
+ createServer((req, res) => { /* router */ }).listen(PORT);
@@ -0,0 +1,23 @@
1
+ // Notifier service — emails / Slack alerts on big charges or failures.
2
+ // ⚠️ Demo drift: ARCHITECTURE.md only mentions 3 services (API, Worker, Scheduler).
3
+ // This fourth one (Notifier) is in code but missing from the architecture doc.
4
+ // DocGuard's Docs-Diff + Docs-Coverage validators surface this.
5
+
6
+ import { Stripe } from './lib/stripe.mjs';
7
+
8
+ export async function notifyLargeCharge(charge) {
9
+ if (charge.amount_cents > 100000) {
10
+ await sendSlack(`💰 Large charge: $${charge.amount_cents / 100}`);
11
+ }
12
+ }
13
+
14
+ export async function notifyFailure(charge, error) {
15
+ await sendEmail({
16
+ to: 'oncall@acme.dev',
17
+ subject: `Charge failed: ${charge.id}`,
18
+ body: error.message,
19
+ });
20
+ }
21
+
22
+ async function sendSlack(text) { /* ... */ }
23
+ async function sendEmail(opts) { /* ... */ }
@@ -0,0 +1,8 @@
1
+ // Scheduler service — cron-style triggers.
2
+ import { enqueue } from './queue.mjs';
3
+
4
+ // Retry failed charges hourly
5
+ setInterval(async () => {
6
+ const failed = await db.query('SELECT id FROM charges WHERE status = $1', ['failed']);
7
+ for (const row of failed.rows) await enqueue({ type: 'process_charge', charge_id: row.id });
8
+ }, 60 * 60 * 1000);
@@ -0,0 +1,15 @@
1
+ // Worker service — consumes job queue.
2
+ import { connect } from './queue.mjs';
3
+
4
+ const handlers = {
5
+ process_charge: async (job) => { /* call Stripe */ },
6
+ process_refund: async (job) => { /* call Stripe refund API */ },
7
+ settle_refund: async (job) => { /* mark refund as settled */ },
8
+ };
9
+
10
+ const queue = await connect(process.env.REDIS_URL);
11
+ queue.consume(async (job) => {
12
+ const handler = handlers[job.type];
13
+ if (!handler) throw new Error(`Unknown job: ${job.type}`);
14
+ await handler(job);
15
+ });