@abhishekchauhan2904/agentforge 0.0.0-stage → 1.5.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 (60) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +162 -2
  3. package/bin/cli.js +204 -0
  4. package/package.json +44 -4
  5. package/src/analyzer.js +103 -0
  6. package/src/api.js +281 -0
  7. package/src/audit.js +27 -0
  8. package/src/checkpoint.js +74 -0
  9. package/src/cloud.js +273 -0
  10. package/src/config.js +24 -0
  11. package/src/dbcheck.js +129 -0
  12. package/src/docker.js +183 -0
  13. package/src/doctor.js +52 -0
  14. package/src/gate.js +67 -0
  15. package/src/generator.js +111 -0
  16. package/src/graph.js +221 -0
  17. package/src/hooks.js +34 -0
  18. package/src/registry.js +45 -0
  19. package/src/secrets.js +44 -0
  20. package/src/services.js +63 -0
  21. package/src/state.js +75 -0
  22. package/src/stats.js +13 -0
  23. package/src/structure.js +107 -0
  24. package/src/validate.js +23 -0
  25. package/templates/agents/architect.md +19 -0
  26. package/templates/agents/aws.md +28 -0
  27. package/templates/agents/azure.md +28 -0
  28. package/templates/agents/backend.md +22 -0
  29. package/templates/agents/database.md +19 -0
  30. package/templates/agents/devops.md +16 -0
  31. package/templates/agents/docker.md +20 -0
  32. package/templates/agents/docs.md +14 -0
  33. package/templates/agents/flutter.md +20 -0
  34. package/templates/agents/frontend.md +19 -0
  35. package/templates/agents/git.md +19 -0
  36. package/templates/agents/microservices.md +30 -0
  37. package/templates/agents/mobile.md +15 -0
  38. package/templates/agents/orchestrator.md +27 -0
  39. package/templates/agents/performance.md +21 -0
  40. package/templates/agents/planner.md +29 -0
  41. package/templates/agents/python.md +20 -0
  42. package/templates/agents/qa.md +26 -0
  43. package/templates/agents/reviewer.md +21 -0
  44. package/templates/agents/security.md +28 -0
  45. package/templates/agents/ui-css.md +15 -0
  46. package/templates/ai/architecture.md +12 -0
  47. package/templates/ai/conventions.md +11 -0
  48. package/templates/ai/decisions.md +6 -0
  49. package/templates/ai/security.md +11 -0
  50. package/templates/cloud/aws/README.md +13 -0
  51. package/templates/cloud/aws/deploy-aws.yml +62 -0
  52. package/templates/cloud/aws/ecr-ecs-deploy-policy.json +21 -0
  53. package/templates/cloud/aws/ecs-task-definition.json +37 -0
  54. package/templates/cloud/aws/github-oidc-trust-policy.json +14 -0
  55. package/templates/cloud/azure/README.md +21 -0
  56. package/templates/cloud/azure/deploy-azure.yml +50 -0
  57. package/templates/cloud/azure/federated-credential.json +6 -0
  58. package/templates/workflows/full.md +10 -0
  59. package/templates/workflows/minimal.md +9 -0
  60. package/templates/workflows/standard.md +8 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 <YOUR NAME>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,163 @@
1
- # Temporary Holding Version
1
+ # agentforge
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Turn **Cursor**, **OpenCode** or **Gemini CLI** into a structured AI engineering team:
4
+ Plan -> Architect -> Develop -> QA -> Security -> Review -> Quality Gate -> Docs -> Git,
5
+ with human approval checkpoints at every step.
6
+
7
+ It does not build your app. It builds the **AI workflow around your app**, and adds real engineering controls that prompt-only frameworks lack.
8
+
9
+ ## Quick start
10
+
11
+ ```bash
12
+ npm install
13
+ npm link # makes `agentforge` available globally
14
+ cd your-project
15
+ agentforge init # pick tool, workflow, specialists
16
+ ```
17
+
18
+ Non-interactive: `agentforge init --yes --tool cursor --workflow full --agents backend,frontend,database`
19
+
20
+ ## What gets generated
21
+
22
+ | Tool | Output |
23
+ |------|--------|
24
+ | Cursor | `.cursor/rules/*.mdc` (orchestrator is always-on) |
25
+ | OpenCode | `.opencode/agents/*.md` + `opencode.json` |
26
+ | Gemini CLI | `GEMINI.md` |
27
+ | All | `.ai/` project memory, `.ai/config.json`, `.ai/reports/` |
28
+
29
+ ## Typical flow
30
+
31
+ ```bash
32
+ agentforge init # once per project
33
+ agentforge install-hooks # commits are blocked if the gate fails
34
+ agentforge start "add admin ticket management"
35
+ agentforge next # tells you what to say to your AI
36
+ # ...agent works, writes .ai/reports/implementation_plan.md...
37
+ agentforge approve # verifies the report, advances to the next step
38
+ agentforge status
39
+ ```
40
+
41
+ `approve` refuses to advance until the step's report exists, has its required sections and does not carry a negative verdict; the gate step runs the real tests, lint, build, audit and secret scan. The workflow is enforced by code, not only by prompts. `--force` overrides and is recorded.
42
+
43
+ ## Commands
44
+
45
+ | Command | Purpose |
46
+ |---------|---------|
47
+ | `init` / `update` / `uninstall` | Install, refresh or remove agent files. `update` and `uninstall` never touch prompts you edited |
48
+ | `start "task"` / `next` / `approve` / `status` | Run a task through the pipeline |
49
+ | `gate [--strict]` | Tests, lint, type-check, build, `npm audit`, secret scan (+ report validation) |
50
+ | `validate [agent]` | Check agent reports against their contracts |
51
+ | `checkpoint [label]` | Snapshot the whole working tree (including new files) without touching your index or branch |
52
+ | `rollback [tag] [--dry-run] [--yes] [--force]` | Restore a checkpoint; lists exactly what changes first |
53
+ | `install-hooks` / `uninstall-hooks` | Git pre-commit hook running the gate |
54
+ | `cloud` / `cloud init aws\|azure` | AWS/Azure usage and IaC/CI security findings (`.ai/cloud.md`); scaffold a secure OIDC deploy workflow |
55
+ | `api` / `db` / `structure` / `audit` | Static API security map, database safety checks, layering and Flutter structure checks (reports in `.ai/*.md`) |
56
+ | `graph [--print]` | Draw the project as Mermaid diagrams (`.ai/graph.md`): services, databases, calls, folder dependencies, cycles |
57
+ | `services` | List detected services (api / frontend / worker) and the databases they need |
58
+ | `docker init` / `docker check` | Scaffold Dockerfile + compose with secure defaults; lint them (secrets block the gate) |
59
+ | `analyze`, `doctor [--prompts]`, `stats` | Refresh context, check setup / lint prompts, gate history |
60
+
61
+ ## AWS and Azure
62
+
63
+ ```bash
64
+ agentforge cloud # what you use + what is dangerous (.ai/cloud.md)
65
+ agentforge docker init # a container image to deploy
66
+ agentforge cloud init aws # or: azure. GitHub Actions + OIDC, no stored keys
67
+ ```
68
+
69
+ | Area | What is checked |
70
+ |------|-----------------|
71
+ | **Identity** | Hardcoded AWS keys, Azure account keys / SAS / client secrets, long-lived keys in CI (use OIDC), `Action *` on `Resource *`, `Principal *` |
72
+ | **Network** | SSH, RDP, MySQL, Postgres, Mongo, Redis ... open to `0.0.0.0/0` in security groups, CloudFormation and Azure NSGs; SQL firewall open to every IP |
73
+ | **Data** | Public S3 ACLs, public access block off, public or unencrypted databases, backups off, storage without HTTPS or with TLS < 1.2, public blobs |
74
+ | **Secrets** | Passwords in Terraform/Bicep, unmarked Bicep secrets, plaintext secrets in Lambda/serverless env, committed `terraform.tfstate` and secret `*.tfvars` |
75
+
76
+ `init` enables the gate item **Cloud Security** when it finds AWS/Azure usage (`cloudCheck` in `.ai/config.json`); only errors block. The **AWS** and **Azure** agents write a design report (services, security, cost, rollback) and are told never to run `terraform apply`, `aws ... delete`, `az group delete` or similar without showing the plan and getting approval. The graph shows which cloud services each service talks to.
77
+
78
+ The scaffolded workflow runs your tests, builds the image, pushes it to ECR/ACR and updates ECS/Container Apps. Follow `infra/aws/README.md` or `infra/azure/README.md` first: you must create the registry, the OIDC role/credential and the runtime yourself.
79
+
80
+ ## API, database and structure checks
81
+
82
+ ```bash
83
+ agentforge audit # api + db + structure; exit code 1 if any error
84
+ agentforge api # .ai/api-map.md: every endpoint with Auth / Roles / Validation / Rate limit
85
+ agentforge db # .ai/db-map.md: models, schema and query-safety findings
86
+ agentforge structure # .ai/structure.md: layering + Flutter structure
87
+ ```
88
+
89
+ | Area | What is checked |
90
+ |------|-----------------|
91
+ | **Authentication** | Every route: is `authenticate`/`Depends(get_current_user)`/`@jwt_required`/DRF `IsAuthenticated` applied (directly, on the router, or at the mount)? Data-changing route without auth = **error** |
92
+ | **Authorization** | Role/permission middleware on admin routes; routes acting on a record by id without an ownership/role check (possible IDOR) |
93
+ | **Middleware** | Validation, rate limiting on login/reset, helmet, CORS, central error handling folder |
94
+ | **Controllers / routes** | routes -> controllers -> services -> models; upward imports are errors; logic inside route files is flagged |
95
+ | **Database** | Password `select:false`, unique email, enums, timestamps, FK indexes, NoSQL/SQL injection, mass assignment |
96
+ | **Python** | FastAPI, Flask, Django REST routes + SQLAlchemy/Django models; Python Docker scaffold |
97
+ | **Flutter** | One state-management approach, features/data layout, data must not import UI, tokens in secure storage, https only, no hardcoded keys |
98
+
99
+ `init` turns the gate checks on when it finds a backend or mobile app (`apiCheck`, `dbCheck`, `structureCheck` in `.ai/config.json`); only **errors** block the gate. The **Python** and **Flutter** agents and the updated **Backend** agent are told to run these commands and fix every error.
100
+
101
+ ## Project graph
102
+
103
+ ```bash
104
+ agentforge graph # writes .ai/graph.md (also done by init / analyze)
105
+ ```
106
+
107
+ `.ai/graph.md` contains Mermaid diagrams that render on GitHub, in VS Code's Markdown preview and at mermaid.live:
108
+
109
+ - **System view:** services, databases/brokers, `user -> frontend`, and service-to-service calls.
110
+ - **Module view (per service):** `routes -> controllers -> models` style folder dependencies with import counts.
111
+ - **Circular dependencies:** listed and drawn in red. Add `"graphCheck": true` to `.ai/config.json` to make cycles block the quality gate.
112
+ - **Language breakdown** of the repository.
113
+
114
+ Agents read the graph before planning, and the Architect/Microservices reports must contain their own Mermaid diagram of the *designed* system (checked by `validate` / `approve`).
115
+
116
+ **Which language is best?** The tool does not pick one for you. The Architect agent defaults to your existing stack and may recommend another only when a concrete requirement forces it, comparing options against fixed criteria (team skills, ecosystem, performance, concurrency, ops cost, hiring, integration).
117
+
118
+ ## Docker and microservices
119
+
120
+ ```bash
121
+ agentforge services # client (frontend), server (api, needs mongo)
122
+ agentforge docker init # Dockerfile + .dockerignore per service, docker-compose.yml with mongo/redis/postgres as needed
123
+ agentforge docker check # lint; runs without a Docker daemon
124
+ ```
125
+
126
+ Scaffolds use pinned images, multi-stage builds, non-root users and healthchecks, and pass `docker check` with zero warnings. Add the **Microservices Architect** only when you really have (or need) several services; the prompt tells it to prefer a modular monolith when a split is not justified. The **Docker Specialist** handles the container; **DevOps** handles CI/CD and cloud.
127
+
128
+ ## Honest limitations
129
+
130
+ - **Cloud: agentforge never connects to your AWS or Azure account.** It cannot see what is really deployed, what a live security group allows, or what anything costs. It reads files in your repo. Terraform, CloudFormation and Bicep are matched with regular expressions (not a real parser), so modules, variables and generated templates can hide problems. For depth also run `tfsec`/`checkov`, `cfn-lint`, `az bicep build`, and AWS Config / Defender for Cloud.
131
+ - The scaffolded deploy workflows were checked for valid YAML/JSON and for passing agentforge's own checks, but **not run against a real AWS or Azure account**. Treat them as a starting point and test in a non-production account first. Only GitHub Actions, ECS Fargate and Azure Container Apps are templated; Lambda, App Service, AKS, EKS and Functions are checked but not scaffolded.
132
+
133
+ - **API/DB/structure checks are static heuristics, not a pentest.** They read code; they do not send requests. Parsed: Express (including `router.route()` chains and mounted routers), FastAPI, Flask, Django REST classes. **Not parsed:** NestJS decorators, Fastify/Koa routes, Django `urls.py` wiring, GraphQL. Auth applied through an unusual helper name can show as "NO": verify before fixing. The IDOR check only sees the handler's own function body.
134
+ - "No index" findings come from the schema files, not the live database. Run `explain` on real queries.
135
+
136
+ - **Graph:** the module view follows relative `import`/`require` in JS/TS only. Path aliases (`@/...`, tsconfig `paths`), Python, Go and other languages are not traced, and service-to-service links are found only when a URL or `<NAME>_URL`-style variable names the other service. Dotted arrows are inferences. Treat the graph as a fast, mostly-right map, not proof.
137
+
138
+ - `docker check` is static analysis (regex on Dockerfile/compose). It does not build images or replace `hadolint`/`docker scout`. The scaffold assumes a Node service listening on port 3000 with a `/health` route; adjust it for your app.
139
+ - Service detection looks one level down (and under `services/`, `apps/`, `packages/`) for `package.json`; non-Node services are not detected yet.
140
+
141
+ - Only **OpenCode** has real subagents. In **Cursor** the Orchestrator is an always-on rule and in **Gemini CLI** all roles live in one `GEMINI.md`; the model follows the process voluntarily. That is why `approve`, the gate and the pre-commit hook exist: they enforce the process in code.
142
+ - Report validation checks structure (sections, length, verdict), not whether a report is truthful. Treat QA/security reports as claims; the gate is the proof.
143
+ - The secret scanner is regex-based: expect some false positives and misses. Use `gitleaks` in CI for depth.
144
+ - `rollback` ends with `git reset -q` (unstages everything; file contents are untouched).
145
+ - Gate commands for Node, Python, Go and Flutter are auto-detected. Edit `.ai/config.json` for anything else.
146
+
147
+ ## Develop / test
148
+
149
+ ```bash
150
+ npm test
151
+ ```
152
+
153
+ ## Add your own agent
154
+
155
+ 1. Create `templates/agents/<name>.md` (Role / Rules / Output / STOP).
156
+ 2. Register it in `src/registry.js` (`AGENTS`, optionally `SPECIALISTS` and `CONTRACTS`).
157
+
158
+ ## Roadmap ideas
159
+
160
+ - Pre-commit hook that runs `agentforge gate`
161
+ - `agentforge doctor` to lint your agent prompts
162
+ - Python / Go gate presets
163
+ - GitHub Action that runs the gate on PRs
package/bin/cli.js ADDED
@@ -0,0 +1,204 @@
1
+ #!/usr/bin/env node
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import prompts from "prompts";
5
+ import { AGENTS, SPECIALISTS, WORKFLOWS } from "../src/registry.js";
6
+ import { generate, uninstall } from "../src/generator.js";
7
+ import { analyze, renderContext } from "../src/analyzer.js";
8
+ import { runGate } from "../src/gate.js";
9
+ import { validateReports } from "../src/validate.js";
10
+ import { createCheckpoint, listCheckpoints, planRollback, rollback } from "../src/checkpoint.js";
11
+ import { installHook, removeHook } from "../src/hooks.js";
12
+ import { loadConfig } from "../src/config.js";
13
+ import { startTask, loadState, describe, approve } from "../src/state.js";
14
+ import { doctor, lintPrompts } from "../src/doctor.js";
15
+ import { readStats } from "../src/stats.js";
16
+ import { dockerInit, lintDocker } from "../src/docker.js";
17
+ import { findServices, isMicroservices } from "../src/services.js";
18
+ import { writeGraph } from "../src/graph.js";
19
+ import { CHECKS, runCheck } from "../src/audit.js";
20
+ import { cloudInit } from "../src/cloud.js";
21
+
22
+ const cwd = process.cwd();
23
+ const [cmd = "help", ...rest] = process.argv.slice(2);
24
+ const flags = {};
25
+ const positional = [];
26
+ for (let i = 0; i < rest.length; i++) {
27
+ if (rest[i].startsWith("--")) {
28
+ const k = rest[i].slice(2);
29
+ flags[k] = rest[i + 1] && !rest[i + 1].startsWith("--") ? rest[++i] : true;
30
+ } else positional.push(rest[i]);
31
+ }
32
+ const c = { g: (s) => `\x1b[32m${s}\x1b[0m`, r: (s) => `\x1b[31m${s}\x1b[0m`, y: (s) => `\x1b[33m${s}\x1b[0m`, b: (s) => `\x1b[1m${s}\x1b[0m`, d: (s) => `\x1b[2m${s}\x1b[0m` };
33
+
34
+ const HELP = `${c.b("agentforge")} - turn your AI coding tool into a structured engineering team
35
+
36
+ ${c.b("Setup")}
37
+ init [--yes --tool T --workflow W --agents a,b] Install agents (cursor | opencode | gemini)
38
+ update [--force] Re-generate agent files; skips ones you edited
39
+ uninstall [--all] Remove generated agent files (--all also removes .ai/)
40
+ analyze Refresh .ai/project-context.md
41
+ doctor [--prompts] Check setup (and lint the prompt templates)
42
+
43
+ ${c.b("Run a task (workflow enforced by code)")}
44
+ start "task" Begin a task: archives old reports, takes a checkpoint, builds the pipeline
45
+ next Show the current step and what to tell your AI
46
+ approve [--force] Verify the step's report (or gate) and advance
47
+ status Show progress
48
+
49
+ ${c.b("Code audits (static, no running server needed)")}
50
+ api Every endpoint with auth / roles / validation / rate-limit, plus findings (.ai/api-map.md)
51
+ db Models, indexes, schema and query-safety findings (.ai/db-map.md)
52
+ structure Layering (routes -> controllers -> services -> models), Flutter structure (.ai/structure.md)
53
+ cloud AWS/Azure services used, IaC + CI findings (.ai/cloud.md)
54
+ cloud init aws|azure Scaffold a secure GitHub-OIDC deploy workflow + templates (never overwrites)
55
+ audit Run api + db + structure + cloud
56
+
57
+ ${c.b("Docker & services")}
58
+ graph [--print] Draw the project: services, databases, calls, module dependencies, cycles (.ai/graph.md)
59
+ services List detected services (api / frontend / worker)
60
+ docker init [--force] Scaffold Dockerfile, .dockerignore and docker-compose.yml (never overwrites)
61
+ docker check Lint Dockerfiles and compose files (errors block the gate)
62
+
63
+ ${c.b("Safety")}
64
+ gate [--strict] Tests, lint, build, audit, secret scan
65
+ validate [agent] Check agent reports against their contracts
66
+ checkpoint [label] Snapshot everything, including new files
67
+ checkpoints List checkpoints
68
+ rollback [tag] [--dry-run] [--yes] [--force]
69
+ install-hooks | uninstall-hooks Block commits when the gate fails
70
+ stats Gate history`;
71
+
72
+ async function init() {
73
+ let { tool, workflow } = flags;
74
+ let specialists = typeof flags.agents === "string" ? flags.agents.split(",").filter(Boolean) : [];
75
+ if (!flags.yes) {
76
+ const info = analyze(cwd);
77
+ const guess = [info.frontend.length && "frontend", info.backend.length && "backend", info.database.length && "database"].filter(Boolean);
78
+ const a = await prompts([
79
+ { type: "select", name: "tool", message: "Which AI tool do you use?", choices: [
80
+ { title: "Cursor", value: "cursor" }, { title: "OpenCode", value: "opencode" }, { title: "Gemini CLI", value: "gemini" }] },
81
+ { type: "select", name: "workflow", message: "Which workflow?", choices: Object.entries(WORKFLOWS).map(([value, w]) => ({ title: w.label, value })) },
82
+ { type: "multiselect", name: "specialists", message: "Pick specialist agents (space to select)", hint: "detected stack is pre-selected",
83
+ choices: SPECIALISTS.map((value) => ({ title: AGENTS[value].title, value, selected: guess.includes(value) })) }
84
+ ], { onCancel: () => process.exit(1) });
85
+ ({ tool, workflow } = a);
86
+ specialists = a.specialists;
87
+ }
88
+ tool ||= "cursor"; workflow ||= "standard";
89
+ report(generate({ tool, workflow, specialists }, cwd, { force: !!flags.force }), true);
90
+ }
91
+
92
+ function report(res, first) {
93
+ console.log(c.g(first ? "\nWorkflow installed." : "\nUpdated.") + ` ${res.agents.length} agents: ${res.agents.join(", ")}`);
94
+ if (res.skipped.length) console.log(c.y(`Kept your edited files (use --force to overwrite): ${res.skipped.join(", ")}`));
95
+ if (first) console.log(`\nNext steps:\n 1. Review ${c.b(".ai/config.json")} (quality-gate commands)\n 2. ${c.b("agentforge install-hooks")} to block bad commits\n 3. ${c.b('agentforge start "your task"')} then ${c.b("agentforge next")}\n`);
96
+ }
97
+
98
+ function gate() {
99
+ const { results, passed } = runGate(cwd, { strictReports: !!flags.strict });
100
+ console.log(c.b("\nQUALITY GATE\n"));
101
+ for (const r of results) console.log(` ${r.name.padEnd(20)} ${r.status === "PASS" ? c.g("PASS") : c.r("FAIL")}`);
102
+ console.log(`\n STATUS: ${passed ? c.g("READY") : c.r("BLOCKED")}`);
103
+ if (!passed) for (const r of results.filter((x) => x.detail)) console.log(`\n${c.b(r.name)}\n${r.detail}`);
104
+ console.log(c.d("\n Report saved to .ai/reports/quality_gate.md\n"));
105
+ process.exit(passed ? 0 : 1);
106
+ }
107
+
108
+ function showStep(state) {
109
+ const d = describe(state);
110
+ if (!d) return console.log(c.g("Task complete. All steps approved."));
111
+ console.log(`${c.b(`Step ${state.index + 1}/${state.pipeline.length}: ${d.step}`)}\n ${d.instruction}`);
112
+ if (d.report) console.log(` Expected report: ${c.b(d.report)}`);
113
+ console.log(c.d(" When the agent is done and you are happy: agentforge approve"));
114
+ }
115
+
116
+ try {
117
+ switch (cmd) {
118
+ case "init": await init(); break;
119
+ case "update": { const cfg = loadConfig(cwd); const sp = cfg.agents.filter((a) => SPECIALISTS.includes(a)); report(generate({ tool: cfg.tool, workflow: cfg.workflow, specialists: sp }, cwd, { force: !!flags.force }), false); break; }
120
+ case "uninstall": { const r = uninstall(cwd, { all: !!flags.all }); console.log(c.g(`Removed ${r.removed.length} files.`) + (r.kept.length ? c.y(` Kept (edited by you): ${r.kept.join(", ")}`) : "")); break; }
121
+ case "analyze": fs.mkdirSync(path.join(cwd, ".ai"), { recursive: true }); fs.writeFileSync(path.join(cwd, ".ai/project-context.md"), renderContext(analyze(cwd))); writeGraph(cwd); console.log(c.g("Updated .ai/project-context.md and .ai/graph.md")); break;
122
+ case "doctor": {
123
+ const checks = doctor(cwd);
124
+ if (flags.prompts) lintPrompts().forEach((m) => checks.push({ level: "warn", msg: `prompt: ${m}` }));
125
+ for (const k of checks) console.log(` ${k.level === "ok" ? c.g("ok ") : k.level === "warn" ? c.y("warn") : c.r("FAIL")} ${k.msg}`);
126
+ process.exit(checks.some((k) => k.level === "fail") ? 1 : 0);
127
+ }
128
+ case "start": { if (!positional[0]) throw new Error('Usage: agentforge start "describe the task"'); const s = startTask(cwd, positional.join(" ")); console.log(c.g("Task started.") + (s.checkpoint ? c.d(` Checkpoint: ${s.checkpoint}`) : c.y(" (no git checkpoint: not a git repo)")) + `\nPipeline: ${s.pipeline.join(" -> ")}\n`); showStep(s); break; }
129
+ case "next": { const s = loadState(cwd); if (!s) throw new Error('No active task. Run `agentforge start "task"`.'); showStep(s); break; }
130
+ case "status": { const s = loadState(cwd); if (!s) throw new Error("No active task."); console.log(`Task: ${s.task}`); s.pipeline.forEach((p, i) => console.log(` ${i < s.index ? c.g("done ") : i === s.index ? c.y("current") : c.d("pending")} ${p}`)); break; }
131
+ case "approve": {
132
+ const r = approve(cwd, { force: !!flags.force });
133
+ if (!r.ok) { console.log(c.r(`Cannot approve "${r.step}":\n - ${r.problems.join("\n - ")}`) + "\n" + c.d("Fix the problems, or use --force to override (recorded).")); process.exit(1); }
134
+ console.log(c.g(`Approved: ${r.step}\n`)); const s = loadState(cwd); showStep(s); break;
135
+ }
136
+ case "validate": { const v = validateReports(cwd, loadConfig(cwd).agents, positional[0] || null); console.log(v.ok ? c.g(`All reports valid: ${v.checked.join(", ") || "(none required)"}`) : c.r("Problems:\n - " + v.problems.join("\n - "))); process.exit(v.ok ? 0 : 1); }
137
+ case "cloud":
138
+ if (positional[0] === "init") {
139
+ const r = cloudInit(cwd, positional[1], { force: !!flags.force });
140
+ r.created.forEach((f) => console.log(c.g("created ") + f)); r.skipped.forEach((f) => console.log(c.y("kept ") + f + " (exists)")); r.notes.forEach((n) => console.log(c.y("note: ") + n));
141
+ console.log(c.d(`\nImage context: ${r.context}. These are templates with <PLACEHOLDERS>: read infra/${positional[1]}/README.md, create the cloud resources, then set the GitHub variables.`));
142
+ break;
143
+ }
144
+ // falls through to the shared audit runner when it is not `cloud init`
145
+ case "api": case "db": case "structure": case "audit": {
146
+ const keys = cmd === "audit" ? Object.keys(CHECKS) : [cmd];
147
+ let errors = 0;
148
+ for (const k of keys) {
149
+ const r = runCheck(cwd, k);
150
+ const e = r.findings.filter((f) => f.level === "error").length, w = r.findings.filter((f) => f.level === "warn").length;
151
+ errors += e;
152
+ console.log(c.b(`\n${CHECKS[k].gate}`) + c.d(` (.ai/${CHECKS[k].out})`));
153
+ if (k === "api") console.log(` ${r.endpoints.length} endpoint(s): ${r.endpoints.filter((x) => x.auth).length} with auth, ${r.endpoints.filter((x) => x.roles).length} with role checks`);
154
+ if (k === "cloud") console.log(` providers: ${r.cloud.providers.join(", ") || "none detected"} services used: ${[...new Set(r.cloud.services.map((x) => `${x.provider}:${x.name}`))].join(", ") || "none"} IaC files: ${r.cloud.iac.length}`);
155
+ if (k === "db") console.log(` ${r.models.length} model(s): ${r.models.map((m) => m.name).join(", ") || "none"}`);
156
+ for (const f of r.findings.filter((x) => x.level !== "info").slice(0, flags.all ? 1e6 : 25)) console.log(` ${f.level === "error" ? c.r("error") : c.y("warn ")} ${f.file}:${f.line} ${f.msg}`);
157
+ if (r.findings.filter((x) => x.level !== "info").length > 25 && !flags.all) console.log(c.d(" ... more in the .ai report (or use --all)"));
158
+ console.log(` ${e} error(s), ${w} warning(s)`);
159
+ }
160
+ process.exit(errors ? 1 : 0);
161
+ }
162
+ case "graph": {
163
+ const g = writeGraph(cwd);
164
+ console.log(c.g("Wrote .ai/graph.md") + c.d(" (Mermaid: renders on GitHub, VS Code preview, mermaid.live)"));
165
+ console.log(` services: ${g.services.length} connections: ${g.system.edges.length} infra: ${g.system.infra.join(", ") || "none"} module views: ${g.modules.length}`);
166
+ console.log(` languages: ${g.languages.slice(0, 4).map(([l, n]) => `${l} ${n}`).join(", ") || "none detected"}`);
167
+ if (g.cycles.length) { console.log(c.y("\n Circular dependencies:")); g.cycles.forEach((x) => console.log(` ${x.service}: ${x.groups.join(" <-> ")}`)); } else console.log(c.g(" No circular dependencies."));
168
+ if (flags.print) console.log("\n" + g.system.mermaid);
169
+ break;
170
+ }
171
+ case "services": { const sv = findServices(cwd); console.log(isMicroservices(sv) ? c.b("Multi-service system") : c.b("Single deployable")); sv.forEach((x) => console.log(` ${x.dir.padEnd(18)} ${x.kind.padEnd(9)} ${x.needs.length ? "needs " + x.needs.join(", ") : ""}`)); break; }
172
+ case "docker": {
173
+ if (positional[0] === "init") { const r = dockerInit(cwd, { force: !!flags.force }); r.created.forEach((f) => console.log(c.g("created ") + f)); r.skipped.forEach((f) => console.log(c.y("kept ") + f + " (exists)")); console.log(c.d("\nNext: edit ports/env, then `agentforge docker check` and `docker compose up --build`.")); break; }
174
+ if (positional[0] === "check") {
175
+ const { files, findings } = lintDocker(cwd);
176
+ if (!files.length) { console.log("No Dockerfile or compose file found. Run `agentforge docker init`."); break; }
177
+ findings.forEach((f) => console.log(` ${f.level === "error" ? c.r("error") : c.y("warn ")} ${f.file}:${f.line} ${f.msg}`));
178
+ const errs = findings.filter((f) => f.level === "error").length;
179
+ console.log(`\n${files.length} file(s), ${errs} error(s), ${findings.length - errs} warning(s)`);
180
+ process.exit(errs ? 1 : 0);
181
+ }
182
+ throw new Error("Usage: agentforge docker init | agentforge docker check");
183
+ }
184
+ case "gate": gate(); break;
185
+ case "stats": {
186
+ const s = readStats(cwd); if (!s) { console.log("No gate runs yet."); break; }
187
+ console.log(`Gate runs: ${s.total} passed: ${s.passed} blocked: ${s.blocked} pass rate: ${s.passRate}%`);
188
+ const top = Object.entries(s.byCheck).sort((a, b) => b[1] - a[1]); if (top.length) console.log("Most common failures: " + top.map(([k, n]) => `${k} (${n})`).join(", ")); break;
189
+ }
190
+ case "checkpoint": { const r = createCheckpoint(cwd, positional[0]); console.log(c.g(`Checkpoint created: ${r.tag}`) + c.d(` (${r.commit})`)); break; }
191
+ case "checkpoints": console.log(listCheckpoints(cwd).join("\n") || "No checkpoints yet."); break;
192
+ case "rollback": {
193
+ const plan = planRollback(cwd, positional[0], { force: !!flags.force });
194
+ console.log(`Checkpoint: ${c.b(plan.target)}\n restore: ${plan.toRestore.length} file(s)${plan.toRestore.map((f) => `\n ~ ${f}`).join("")}\n delete: ${plan.toDelete.length} new file(s)${plan.toDelete.map((f) => `\n - ${f}`).join("")}`);
195
+ if (flags["dry-run"]) break;
196
+ if (!flags.yes) { console.log(c.y("\nNothing changed. Re-run with --yes to apply (or --dry-run to only preview).")); process.exit(1); }
197
+ rollback(cwd, positional[0], { force: !!flags.force }); console.log(c.g("\nRolled back.")); break;
198
+ }
199
+ case "install-hooks": console.log(c.g("Installed: ") + installHook(cwd, { force: !!flags.force })); break;
200
+ case "uninstall-hooks": console.log(removeHook(cwd) ? c.g("Hook removed.") : "No agentforge hook found."); break;
201
+ case "help": case "--help": case "-h": console.log(HELP); break;
202
+ default: console.log(HELP); process.exit(1);
203
+ }
204
+ } catch (e) { console.error(c.r("Error: ") + e.message); process.exit(1); }
package/package.json CHANGED
@@ -1,6 +1,46 @@
1
1
  {
2
2
  "name": "@abhishekchauhan2904/agentforge",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.5.0",
4
+ "description": "Turn Cursor, OpenCode or Gemini CLI into a structured AI software team with quality gates, rollback and project memory.",
5
+ "type": "module",
6
+ "bin": {
7
+ "agentforge": "./bin/cli.js"
8
+ },
9
+ "files": [
10
+ "bin",
11
+ "src",
12
+ "templates"
13
+ ],
14
+ "scripts": {
15
+ "test": "node --test test/*.test.js",
16
+ "prepublishOnly": "npm test"
17
+ },
18
+ "engines": {
19
+ "node": ">=18"
20
+ },
21
+ "keywords": [
22
+ "ai",
23
+ "agents",
24
+ "cursor",
25
+ "opencode",
26
+ "gemini-cli",
27
+ "workflow",
28
+ "cli"
29
+ ],
30
+ "license": "MIT",
31
+ "dependencies": {
32
+ "prompts": "^2.4.2"
33
+ },
34
+ "author": "Abhishek Chauhan <singh.21abhishek@gmail.com>",
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "git+https://github.com/AbhishekChauhan2904/agentforge.git"
38
+ },
39
+ "homepage": "https://github.com/AbhishekChauhan2904/agentforge#readme",
40
+ "bugs": {
41
+ "url": "https://github.com/AbhishekChauhan2904/agentforge/issues"
42
+ },
43
+ "publishConfig": {
44
+ "access": "public"
45
+ }
46
+ }
@@ -0,0 +1,103 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { findServices, isMicroservices } from "./services.js";
4
+ import { detectCloud } from "./cloud.js";
5
+
6
+ const read = (p) => { try { return fs.readFileSync(p, "utf8"); } catch { return null; } };
7
+ const exists = (cwd, f) => fs.existsSync(path.join(cwd, f));
8
+
9
+ const DEP_MAP = {
10
+ frontend: { react: "React", next: "Next.js", vue: "Vue", svelte: "Svelte", "@angular/core": "Angular", tailwindcss: "Tailwind CSS" },
11
+ backend: { express: "Express", fastify: "Fastify", "@nestjs/core": "NestJS", koa: "Koa" },
12
+ database: { mongoose: "MongoDB (Mongoose)", mongodb: "MongoDB", pg: "PostgreSQL", mysql2: "MySQL", prisma: "Prisma", "@prisma/client": "Prisma", sequelize: "Sequelize", redis: "Redis", ioredis: "Redis" },
13
+ auth: { jsonwebtoken: "JWT", passport: "Passport", bcrypt: "bcrypt", bcryptjs: "bcrypt", "next-auth": "NextAuth" },
14
+ testing: { jest: "Jest", vitest: "Vitest", mocha: "Mocha", supertest: "Supertest", cypress: "Cypress", "@playwright/test": "Playwright" }
15
+ };
16
+
17
+ export function analyze(cwd) {
18
+ const info = { name: path.basename(cwd), languages: [], frontend: [], backend: [], database: [], auth: [], testing: [], scripts: {}, deploy: [], tree: [], services: [], microservices: false, cloud: [] };
19
+ const pkgRaw = read(path.join(cwd, "package.json"));
20
+ if (pkgRaw) {
21
+ try {
22
+ const pkg = JSON.parse(pkgRaw);
23
+ info.name = pkg.name || info.name;
24
+ info.languages.push(exists(cwd, "tsconfig.json") ? "TypeScript" : "JavaScript");
25
+ info.scripts = pkg.scripts || {};
26
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
27
+ for (const [cat, map] of Object.entries(DEP_MAP))
28
+ for (const [dep, label] of Object.entries(map))
29
+ if (deps[dep] && !info[cat].includes(label)) info[cat].push(label);
30
+ } catch { /* invalid package.json: ignore */ }
31
+ }
32
+ if (exists(cwd, "requirements.txt") || exists(cwd, "pyproject.toml")) {
33
+ info.languages.push("Python");
34
+ info.pyText = ((read(path.join(cwd, "requirements.txt")) || "") + (read(path.join(cwd, "pyproject.toml")) || "")).toLowerCase();
35
+ for (const [d, l] of [["django", "Django"], ["flask", "Flask"], ["fastapi", "FastAPI"]]) if (info.pyText.includes(d)) info.backend.push(l);
36
+ for (const [d, l] of [["pytest", "pytest"]]) if (info.pyText.includes(d) || exists(cwd, "tests")) info.testing.push(l);
37
+ }
38
+ if (exists(cwd, "pubspec.yaml")) {
39
+ info.languages.push("Dart"); info.frontend.push("Flutter");
40
+ const pub = read(path.join(cwd, "pubspec.yaml")) || "";
41
+ for (const [k, l] of [["flutter_bloc", "BLoC"], ["provider", "Provider"], ["riverpod", "Riverpod"], ["mobx", "MobX"]]) if (pub.includes(k)) info.frontend.push(l);
42
+ }
43
+ if (exists(cwd, "go.mod")) { info.languages.push("Go"); info.testing.push("go test"); }
44
+ if (exists(cwd, "Dockerfile") || exists(cwd, "docker-compose.yml")) info.deploy.push("Docker");
45
+ if (exists(cwd, ".github/workflows")) info.deploy.push("GitHub Actions");
46
+ if (exists(cwd, "nginx.conf")) info.deploy.push("Nginx");
47
+ info.services = findServices(cwd);
48
+ info.microservices = isMicroservices(info.services);
49
+ try { info.cloud = detectCloud(cwd).providers.map((p) => ({ aws: "AWS", azure: "Azure" }[p])); } catch { /* ignore */ }
50
+ try {
51
+ info.tree = fs.readdirSync(cwd, { withFileTypes: true })
52
+ .filter((d) => !d.name.startsWith(".") && d.name !== "node_modules")
53
+ .map((d) => d.name + (d.isDirectory() ? "/" : ""));
54
+ } catch { /* ignore */ }
55
+ return info;
56
+ }
57
+
58
+ // Choose gate commands from the scripts the project actually has.
59
+ export function detectGateCommands(info) {
60
+ const s = info.scripts;
61
+ const gate = [];
62
+ if (s.test && !/no test specified/.test(s.test)) gate.push({ name: "Tests", cmd: "npm test --silent" });
63
+ if (s.lint) gate.push({ name: "Lint", cmd: "npm run lint --silent" });
64
+ if (s.typecheck) gate.push({ name: "Type Check", cmd: "npm run typecheck --silent" });
65
+ else if (info.languages.includes("TypeScript")) gate.push({ name: "Type Check", cmd: "npx tsc --noEmit" });
66
+ if (s.build) gate.push({ name: "Build", cmd: "npm run build --silent" });
67
+ if (info.languages.includes("Python")) {
68
+ if (info.testing.includes("pytest")) gate.push({ name: "Tests", cmd: "python -m pytest -q" });
69
+ if ((info.pyText || "").includes("ruff")) gate.push({ name: "Lint", cmd: "ruff check ." });
70
+ }
71
+ if (info.languages.includes("Go")) { gate.push({ name: "Tests", cmd: "go test ./..." }); gate.push({ name: "Vet", cmd: "go vet ./..." }); }
72
+ if (info.frontend.includes("Flutter")) { gate.push({ name: "Analyze", cmd: "flutter analyze" }); gate.push({ name: "Tests", cmd: "flutter test" }); }
73
+ return gate;
74
+ }
75
+
76
+ export function renderContext(info) {
77
+ const list = (a) => (a.length ? a.join(", ") : "not detected");
78
+ return `# Project Context (auto-generated by agentforge)
79
+
80
+ Every agent MUST read this file before working. Update it when the stack changes.
81
+
82
+ | Item | Value |
83
+ |------|-------|
84
+ | Name | ${info.name} |
85
+ | Languages | ${list(info.languages)} |
86
+ | Frontend | ${list(info.frontend)} |
87
+ | Backend | ${list(info.backend)} |
88
+ | Database | ${list(info.database)} |
89
+ | Auth | ${list(info.auth)} |
90
+ | Testing | ${list(info.testing)} |
91
+ | Deployment | ${list(info.deploy)} |
92
+ | Cloud | ${list(info.cloud)} |
93
+
94
+ ## Services (${info.microservices ? "multi-service system" : "single deployable"})
95
+ ${info.services.length ? info.services.map((x) => `- \`${x.dir}\` (${x.kind})${x.needs.length ? " needs " + x.needs.join(", ") : ""}`).join("\n") : "- none detected"}
96
+
97
+ ## npm scripts
98
+ ${Object.keys(info.scripts).length ? Object.entries(info.scripts).map(([k, v]) => `- \`${k}\`: \`${v}\``).join("\n") : "- none"}
99
+
100
+ ## Top-level layout
101
+ ${info.tree.length ? info.tree.map((t) => `- ${t}`).join("\n") : "- empty project"}
102
+ `;
103
+ }