@rijalpermana/spec-forge 0.1.0 → 0.1.2
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.
- package/README.md +3 -0
- package/dist/cli.js +100 -38
- package/package.json +1 -1
- package/templates/rules.md +71 -0
package/README.md
CHANGED
|
@@ -63,6 +63,7 @@ forge --version
|
|
|
63
63
|
cd your-project
|
|
64
64
|
forge init # scaffolds specs/ + AI bridge (defaults to Claude Code)
|
|
65
65
|
forge scan # (optional) inventory existing code -> specs/CODEBASE.md
|
|
66
|
+
forge rules # (optional) scaffold specs/RULES.md — project conventions
|
|
66
67
|
forge smelt btn-fraud-check # interactive Q&A -> brief.md + prd.md stub
|
|
67
68
|
|
|
68
69
|
# draft prd.md by hand, or with your AI tool, using brief.md as grounding
|
|
@@ -127,6 +128,7 @@ the writing.
|
|
|
127
128
|
| `forge init [--target <t>]` | — | `specs/`, `specs/INDEX.md`, AI bridge files | Yes — bridge files regenerate freely |
|
|
128
129
|
| `forge bridge <target>` | — | AI bridge files for `<target>` | Yes — always regenerates |
|
|
129
130
|
| `forge scan [--depth <n>]` | — | `specs/CODEBASE.md` (stack, file stats, existing schema/API files, directory tree) | Yes — refreshes on every run |
|
|
131
|
+
| `forge rules` | — | `specs/RULES.md` (project conventions the AI grounds all drafting in) | Yes — refuses if `RULES.md` exists |
|
|
130
132
|
| `forge smelt <feature>` | — | `brief.md`, `prd.md` (stub), `INDEX.md` entry (`draft`) | Yes — refuses if `brief.md` exists |
|
|
131
133
|
| `forge schema <feature>` | `prd.md` | `schema.dbml` (stub) | Yes |
|
|
132
134
|
| `forge contract <feature>` | `prd.md` | `api-contract.md` (stub) | Yes |
|
|
@@ -141,6 +143,7 @@ your-project/
|
|
|
141
143
|
├── specs/
|
|
142
144
|
│ ├── INDEX.md # creation order, status, dependencies across all features
|
|
143
145
|
│ ├── CODEBASE.md # (optional) forge scan output: existing project inventory
|
|
146
|
+
│ ├── RULES.md # (optional) forge rules output: project conventions the AI follows
|
|
144
147
|
│ └── <feature-name>/
|
|
145
148
|
│ ├── brief.md # smelt Q&A capture
|
|
146
149
|
│ ├── prd.md # goal, actors, user stories, business rules, out-of-scope
|
package/dist/cli.js
CHANGED
|
@@ -6821,6 +6821,7 @@ function featureDir(feature, cwd = process.cwd()) {
|
|
|
6821
6821
|
}
|
|
6822
6822
|
|
|
6823
6823
|
// src/lib/command-specs.ts
|
|
6824
|
+
var GROUND_IN_RULES = "If specs/RULES.md exists, read it first and follow those project conventions — " + "they override any defaults described below. ";
|
|
6824
6825
|
var COMMAND_SPECS = [
|
|
6825
6826
|
{
|
|
6826
6827
|
name: "scan",
|
|
@@ -6830,13 +6831,21 @@ var COMMAND_SPECS = [
|
|
|
6830
6831
|
readOnly: true,
|
|
6831
6832
|
instructions: "This command scans the existing project deterministically (no code is " + "interpreted) and writes specs/CODEBASE.md — a factual inventory of the detected " + "stack, file statistics, existing schema and API files, and the directory tree. " + "After it runs, read specs/CODEBASE.md and use it as grounding: when drafting " + "schemas, contracts, or tasks later, reuse the conventions, entities, and files " + "that already exist here instead of inventing new ones. Don't echo the whole file " + "back — just confirm the stack and the spec-relevant files you found."
|
|
6832
6833
|
},
|
|
6834
|
+
{
|
|
6835
|
+
name: "rules",
|
|
6836
|
+
argumentHint: "(no arguments — scaffolds specs/RULES.md)",
|
|
6837
|
+
description: "Scaffold specs/RULES.md — project conventions the AI follows when drafting",
|
|
6838
|
+
requiresPrd: false,
|
|
6839
|
+
readOnly: false,
|
|
6840
|
+
instructions: "This command scaffolds specs/RULES.md — a project-level conventions file " + "(tech stack, naming, module structure, API/response format, data, validation, " + "security, testing, docs, tooling). After it runs, help the user fill the [TODO] " + "sections: infer what you can from specs/CODEBASE.md and the existing code, and " + "ask about anything you can't. Keep each rule factual and enforceable. Once " + "filled, every other forge drafting command must obey these rules."
|
|
6841
|
+
},
|
|
6833
6842
|
{
|
|
6834
6843
|
name: "smelt",
|
|
6835
6844
|
argumentHint: "[feature-name]",
|
|
6836
6845
|
description: "Extract raw requirements into a grounding brief, then draft the PRD",
|
|
6837
6846
|
requiresPrd: false,
|
|
6838
6847
|
readOnly: false,
|
|
6839
|
-
instructions: "This command
|
|
6848
|
+
instructions: GROUND_IN_RULES + "This command has two input modes. Default: it prompts for goal, actors, " + "constraints, and out-of-scope items in the terminal. With --from <file>: it " + "stages an existing BRD/requirements document verbatim in the feature folder as " + "source-brd.<ext> (extension preserved — .md, .pdf, .docx, …) and leaves " + "'[TODO: extract from source-brd.<ext>]' markers in {{brief}}. If a source-brd.* " + "file exists, read it (if your tool can't open that format — e.g. .docx — say so " + "and ask the user to export it to Markdown or PDF) and replace each marker in " + "{{brief}} with the value extracted from the document, citing the BRD section it " + "came from so every requirement stays traceable. Then read {{brief}} and draft " + "{{prd}} following the structure already stubbed there. Do not invent scope, " + "actors, or rules that aren't grounded in the brief or the BRD — if something's " + "missing, ask what to clarify instead of guessing."
|
|
6840
6849
|
},
|
|
6841
6850
|
{
|
|
6842
6851
|
name: "schema",
|
|
@@ -6844,7 +6853,7 @@ var COMMAND_SPECS = [
|
|
|
6844
6853
|
description: "Scaffold schema.dbml, then draft it from prd.md",
|
|
6845
6854
|
requiresPrd: true,
|
|
6846
6855
|
readOnly: false,
|
|
6847
|
-
instructions: "Read {{prd}} and fill {{schema}} with tables, columns, and relationships that " + "are directly traceable to entities, actors, or business rules in the PRD. " + "Follow existing DBML conventions in this repo (multi-tenant RLS pattern, " + "tenant_id scoping, Drizzle-compatible types) if a reference schema exists " + "elsewhere in the project."
|
|
6856
|
+
instructions: GROUND_IN_RULES + "Read {{prd}} and fill {{schema}} with tables, columns, and relationships that " + "are directly traceable to entities, actors, or business rules in the PRD. " + "Follow existing DBML conventions in this repo (multi-tenant RLS pattern, " + "tenant_id scoping, Drizzle-compatible types) if a reference schema exists " + "elsewhere in the project."
|
|
6848
6857
|
},
|
|
6849
6858
|
{
|
|
6850
6859
|
name: "contract",
|
|
@@ -6852,7 +6861,7 @@ var COMMAND_SPECS = [
|
|
|
6852
6861
|
description: "Scaffold api-contract.md, then draft it from prd.md",
|
|
6853
6862
|
requiresPrd: true,
|
|
6854
6863
|
readOnly: false,
|
|
6855
|
-
instructions: "Read {{prd}} and fill {{contract}}. Use exactly this format per endpoint, " + `one block per endpoint, no deviation:
|
|
6864
|
+
instructions: GROUND_IN_RULES + "Read {{prd}} and fill {{contract}}. Use exactly this format per endpoint, " + `one block per endpoint, no deviation:
|
|
6856
6865
|
|
|
6857
6866
|
` + `Title: [short name]
|
|
6858
6867
|
` + `endpoint: [METHOD] [path]
|
|
@@ -6866,7 +6875,7 @@ var COMMAND_SPECS = [
|
|
|
6866
6875
|
description: "Scaffold tasks.md, then draft it from prd.md",
|
|
6867
6876
|
requiresPrd: true,
|
|
6868
6877
|
readOnly: false,
|
|
6869
|
-
instructions: "Read {{prd}} and {{schema}} (if present), then fill {{tasks}}. Sequence " + "strictly: data/schema tasks before logic tasks before UI tasks. Call out any " + "dependency inversion explicitly rather than silently reordering — if the PRD " + "implies model or logic work before its data source exists, flag it instead of " + "hiding it."
|
|
6878
|
+
instructions: GROUND_IN_RULES + "Read {{prd}} and {{schema}} (if present), then fill {{tasks}}. Sequence " + "strictly: data/schema tasks before logic tasks before UI tasks. Call out any " + "dependency inversion explicitly rather than silently reordering — if the PRD " + "implies model or logic work before its data source exists, flag it instead of " + "hiding it."
|
|
6870
6879
|
},
|
|
6871
6880
|
{
|
|
6872
6881
|
name: "testcase",
|
|
@@ -6874,7 +6883,7 @@ var COMMAND_SPECS = [
|
|
|
6874
6883
|
description: "Scaffold testcases.md, then draft it from prd.md",
|
|
6875
6884
|
requiresPrd: true,
|
|
6876
6885
|
readOnly: false,
|
|
6877
|
-
instructions: "Read {{prd}} and fill {{testcases}}. Use exactly this table format, one row " + `per scenario, do not change columns:
|
|
6886
|
+
instructions: GROUND_IN_RULES + "Read {{prd}} and fill {{testcases}}. Use exactly this table format, one row " + `per scenario, do not change columns:
|
|
6878
6887
|
|
|
6879
6888
|
` + `| No | Test Scenario | Test Case | Test Type | Expected Result | Actual Result | Status | Remark |
|
|
6880
6889
|
` + `| ------ | ------ | ------ | ------ | ------ | ------ | ------ | ------ |
|
|
@@ -7146,7 +7155,8 @@ Next: forge smelt <feature-name>`);
|
|
|
7146
7155
|
|
|
7147
7156
|
// src/commands/smelt.ts
|
|
7148
7157
|
var import_prompts = __toESM(require_prompts3(), 1);
|
|
7149
|
-
import {
|
|
7158
|
+
import { copyFileSync, mkdirSync as mkdirSync5 } from "node:fs";
|
|
7159
|
+
import { basename, extname, join as join8, resolve } from "node:path";
|
|
7150
7160
|
|
|
7151
7161
|
// src/lib/template.ts
|
|
7152
7162
|
import { readFileSync as readFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "node:fs";
|
|
@@ -7169,7 +7179,8 @@ function fileExists(path) {
|
|
|
7169
7179
|
}
|
|
7170
7180
|
|
|
7171
7181
|
// src/commands/smelt.ts
|
|
7172
|
-
|
|
7182
|
+
var SOURCE_DOC_BASE = "source-brd";
|
|
7183
|
+
async function smelt(feature, opts = {}) {
|
|
7173
7184
|
const cwd = process.cwd();
|
|
7174
7185
|
const dir = featureDir(feature, cwd);
|
|
7175
7186
|
const briefPath = join8(dir, SPEC_FILES.brief);
|
|
@@ -7186,36 +7197,20 @@ async function smelt(feature) {
|
|
|
7186
7197
|
console.log(`Existing features: ${existingFeatures.join(", ")}
|
|
7187
7198
|
`);
|
|
7188
7199
|
}
|
|
7189
|
-
const
|
|
7190
|
-
|
|
7191
|
-
{ type: "text", name: "actors", message: "Who are the actors/roles involved?" },
|
|
7192
|
-
{ type: "text", name: "constraints", message: "Key constraints (regulatory, technical, deadline)?" },
|
|
7193
|
-
{ type: "text", name: "outOfScope", message: "What's explicitly OUT of scope?" },
|
|
7194
|
-
{
|
|
7195
|
-
type: "text",
|
|
7196
|
-
name: "dependsOn",
|
|
7197
|
-
message: "Does this depend on another existing feature? (feature name, or leave blank)"
|
|
7198
|
-
}
|
|
7199
|
-
]);
|
|
7200
|
-
if (!answers.goal) {
|
|
7201
|
-
console.log("Smelting cancelled.");
|
|
7200
|
+
const briefVars = opts.from ? stageSourceDoc(opts.from, dir) : await runInteractive(feature);
|
|
7201
|
+
if (!briefVars)
|
|
7202
7202
|
return;
|
|
7203
|
-
}
|
|
7204
7203
|
const briefRaw = readTemplate(join8(templatesDir, SPEC_FILES.brief));
|
|
7205
7204
|
const brief = renderTemplate(briefRaw, {
|
|
7206
7205
|
feature,
|
|
7207
7206
|
date: new Date().toISOString().slice(0, 10),
|
|
7208
|
-
|
|
7209
|
-
actors: answers.actors ?? "",
|
|
7210
|
-
constraints: answers.constraints ?? "",
|
|
7211
|
-
outOfScope: answers.outOfScope ?? ""
|
|
7207
|
+
...briefVars
|
|
7212
7208
|
});
|
|
7213
7209
|
writeIfAbsent(briefPath, brief);
|
|
7214
7210
|
const prdRaw = readTemplate(join8(templatesDir, SPEC_FILES.prd));
|
|
7215
7211
|
const prd = renderTemplate(prdRaw, { feature });
|
|
7216
7212
|
const prdWritten = writeIfAbsent(prdPath, prd);
|
|
7217
|
-
|
|
7218
|
-
upsertIndexEntry(cwd, feature, { status: "draft", dependsOn });
|
|
7213
|
+
upsertIndexEntry(cwd, feature, { status: "draft", dependsOn: briefVars.dependsOn });
|
|
7219
7214
|
console.log(`
|
|
7220
7215
|
Wrote ${briefPath}`);
|
|
7221
7216
|
if (prdWritten)
|
|
@@ -7224,6 +7219,55 @@ Wrote ${briefPath}`);
|
|
|
7224
7219
|
console.log(`
|
|
7225
7220
|
Next: open this project in your AI tool and draft prd.md from brief.md ` + `— use the /forge:smelt, /forge-smelt, or AGENTS.md instructions, depending ` + `on which bridge you initialized.`);
|
|
7226
7221
|
}
|
|
7222
|
+
function stageSourceDoc(from, dir) {
|
|
7223
|
+
const srcPath = resolve(process.cwd(), from);
|
|
7224
|
+
if (!fileExists(srcPath)) {
|
|
7225
|
+
console.log(`Source document not found: ${srcPath}`);
|
|
7226
|
+
process.exitCode = 1;
|
|
7227
|
+
return;
|
|
7228
|
+
}
|
|
7229
|
+
const docName = `${SOURCE_DOC_BASE}${extname(srcPath).toLowerCase()}`;
|
|
7230
|
+
const destPath = join8(dir, docName);
|
|
7231
|
+
if (fileExists(destPath)) {
|
|
7232
|
+
console.log(`${destPath} already exists — leaving it in place.`);
|
|
7233
|
+
} else {
|
|
7234
|
+
mkdirSync5(dir, { recursive: true });
|
|
7235
|
+
copyFileSync(srcPath, destPath);
|
|
7236
|
+
console.log(`Staged ${basename(srcPath)} → ${destPath}`);
|
|
7237
|
+
}
|
|
7238
|
+
const marker = `[TODO: extract from ${docName}]`;
|
|
7239
|
+
return {
|
|
7240
|
+
goal: marker,
|
|
7241
|
+
actors: marker,
|
|
7242
|
+
constraints: marker,
|
|
7243
|
+
outOfScope: marker,
|
|
7244
|
+
dependsOn: "-"
|
|
7245
|
+
};
|
|
7246
|
+
}
|
|
7247
|
+
async function runInteractive(feature) {
|
|
7248
|
+
const answers = await import_prompts.default([
|
|
7249
|
+
{ type: "text", name: "goal", message: "One-sentence goal — what must this feature deliver?" },
|
|
7250
|
+
{ type: "text", name: "actors", message: "Who are the actors/roles involved?" },
|
|
7251
|
+
{ type: "text", name: "constraints", message: "Key constraints (regulatory, technical, deadline)?" },
|
|
7252
|
+
{ type: "text", name: "outOfScope", message: "What's explicitly OUT of scope?" },
|
|
7253
|
+
{
|
|
7254
|
+
type: "text",
|
|
7255
|
+
name: "dependsOn",
|
|
7256
|
+
message: "Does this depend on another existing feature? (feature name, or leave blank)"
|
|
7257
|
+
}
|
|
7258
|
+
]);
|
|
7259
|
+
if (!answers.goal) {
|
|
7260
|
+
console.log("Smelting cancelled.");
|
|
7261
|
+
return null;
|
|
7262
|
+
}
|
|
7263
|
+
return {
|
|
7264
|
+
goal: answers.goal ?? "",
|
|
7265
|
+
actors: answers.actors ?? "",
|
|
7266
|
+
constraints: answers.constraints ?? "",
|
|
7267
|
+
outOfScope: answers.outOfScope ?? "",
|
|
7268
|
+
dependsOn: answers.dependsOn?.trim() || "-"
|
|
7269
|
+
};
|
|
7270
|
+
}
|
|
7227
7271
|
|
|
7228
7272
|
// src/commands/scaffold.ts
|
|
7229
7273
|
import { join as join9 } from "node:path";
|
|
@@ -7290,8 +7334,8 @@ function verify(feature) {
|
|
|
7290
7334
|
}
|
|
7291
7335
|
|
|
7292
7336
|
// src/commands/scan.ts
|
|
7293
|
-
import { existsSync as existsSync4, readFileSync as readFileSync4, readdirSync, mkdirSync as
|
|
7294
|
-
import { join as join11, extname, relative, basename } from "node:path";
|
|
7337
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4, readdirSync, mkdirSync as mkdirSync6, writeFileSync as writeFileSync5 } from "node:fs";
|
|
7338
|
+
import { join as join11, extname as extname2, relative, basename as basename2 } from "node:path";
|
|
7295
7339
|
var IGNORE_DIRS = new Set([
|
|
7296
7340
|
"node_modules",
|
|
7297
7341
|
".git",
|
|
@@ -7331,7 +7375,7 @@ function scan(options = {}) {
|
|
|
7331
7375
|
apis: pathListOrNone(apis),
|
|
7332
7376
|
tree: renderTree(cwd, depth)
|
|
7333
7377
|
});
|
|
7334
|
-
|
|
7378
|
+
mkdirSync6(specsDir(cwd), { recursive: true });
|
|
7335
7379
|
const outPath = join11(specsDir(cwd), "CODEBASE.md");
|
|
7336
7380
|
writeFileSync5(outPath, rendered, "utf-8");
|
|
7337
7381
|
console.log(`Wrote ${outPath}`);
|
|
@@ -7418,8 +7462,8 @@ function detectStack(root) {
|
|
|
7418
7462
|
return found;
|
|
7419
7463
|
}
|
|
7420
7464
|
function isSchemaFile(f) {
|
|
7421
|
-
const ext =
|
|
7422
|
-
const base =
|
|
7465
|
+
const ext = extname2(f).toLowerCase();
|
|
7466
|
+
const base = basename2(f).toLowerCase();
|
|
7423
7467
|
if (ext === ".dbml" || ext === ".prisma" || ext === ".sql")
|
|
7424
7468
|
return true;
|
|
7425
7469
|
if (/schema/.test(base) && [".ts", ".js", ".py", ".rb", ".go", ".json"].includes(ext))
|
|
@@ -7430,7 +7474,7 @@ function isSchemaFile(f) {
|
|
|
7430
7474
|
}
|
|
7431
7475
|
function isApiFile(f) {
|
|
7432
7476
|
const p = f.toLowerCase();
|
|
7433
|
-
const base =
|
|
7477
|
+
const base = basename2(p);
|
|
7434
7478
|
if (/(openapi|swagger)\.(ya?ml|json)$/.test(base))
|
|
7435
7479
|
return true;
|
|
7436
7480
|
if (/\.(controller|route|router|resolver)\.(ts|js|py)$/.test(base))
|
|
@@ -7440,7 +7484,7 @@ function isApiFile(f) {
|
|
|
7440
7484
|
return false;
|
|
7441
7485
|
}
|
|
7442
7486
|
function renderProject(root, fileCount) {
|
|
7443
|
-
let name =
|
|
7487
|
+
let name = basename2(root);
|
|
7444
7488
|
const pkgPath = join11(root, "package.json");
|
|
7445
7489
|
if (existsSync4(pkgPath)) {
|
|
7446
7490
|
try {
|
|
@@ -7471,7 +7515,7 @@ function renderProject(root, fileCount) {
|
|
|
7471
7515
|
function fileStats(files) {
|
|
7472
7516
|
const counts = new Map;
|
|
7473
7517
|
for (const f of files) {
|
|
7474
|
-
const ext =
|
|
7518
|
+
const ext = extname2(f).toLowerCase() || "(no ext)";
|
|
7475
7519
|
counts.set(ext, (counts.get(ext) ?? 0) + 1);
|
|
7476
7520
|
}
|
|
7477
7521
|
const top = [...counts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 12);
|
|
@@ -7479,7 +7523,7 @@ function fileStats(files) {
|
|
|
7479
7523
|
`);
|
|
7480
7524
|
}
|
|
7481
7525
|
function renderTree(root, maxDepth) {
|
|
7482
|
-
const lines = [`${
|
|
7526
|
+
const lines = [`${basename2(root) || root}/`];
|
|
7483
7527
|
walkTree(root, "", maxDepth, lines);
|
|
7484
7528
|
return lines.join(`
|
|
7485
7529
|
`);
|
|
@@ -7525,13 +7569,31 @@ function linesOrNone(items, emptyText) {
|
|
|
7525
7569
|
`) : emptyText;
|
|
7526
7570
|
}
|
|
7527
7571
|
|
|
7572
|
+
// src/commands/rules.ts
|
|
7573
|
+
import { join as join12 } from "node:path";
|
|
7574
|
+
function rules() {
|
|
7575
|
+
const cwd = process.cwd();
|
|
7576
|
+
const outPath = join12(specsDir(cwd), "RULES.md");
|
|
7577
|
+
const raw = readTemplate(join12(templatesDir, "rules.md"));
|
|
7578
|
+
const written = writeIfAbsent(outPath, raw);
|
|
7579
|
+
if (!written) {
|
|
7580
|
+
console.log(`${outPath} already exists — edit it directly, or delete it to regenerate the template.`);
|
|
7581
|
+
return;
|
|
7582
|
+
}
|
|
7583
|
+
console.log(`Wrote ${outPath}`);
|
|
7584
|
+
console.log(` Fill in the [TODO] sections with your project's conventions.`);
|
|
7585
|
+
console.log(`
|
|
7586
|
+
Every forge drafting command (smelt, schema, contract, tasks, testcase) ` + `will ground the AI in these rules.`);
|
|
7587
|
+
}
|
|
7588
|
+
|
|
7528
7589
|
// src/cli.ts
|
|
7529
7590
|
var program2 = new Command;
|
|
7530
|
-
program2.name("forge").description("Spec-driven development CLI — scaffolds specs, schemas, API contracts, tasks, and test cases.").version("0.1.
|
|
7591
|
+
program2.name("forge").description("Spec-driven development CLI — scaffolds specs, schemas, API contracts, tasks, and test cases.").version("0.1.2");
|
|
7531
7592
|
program2.command("init").description("Scaffold specs/ folder and an AI-tool bridge in the current project").option("-t, --target <target>", "bridge target: claude, cursor, windsurf, or generic", "claude").action((options) => init({ target: options.target }));
|
|
7532
7593
|
program2.command("bridge <target>").description("(Re)generate AI-tool bridge commands: claude, cursor, windsurf, or generic").action((target) => bridge(target));
|
|
7533
7594
|
program2.command("scan").description("Inventory the existing project structure into specs/CODEBASE.md").option("-d, --depth <n>", "directory-tree depth to include in the report", "2").action((options) => scan({ depth: Number(options.depth) }));
|
|
7534
|
-
program2.command("
|
|
7595
|
+
program2.command("rules").description("Scaffold specs/RULES.md — project conventions the AI follows when drafting").action(() => rules());
|
|
7596
|
+
program2.command("smelt <feature>").description("Extract raw requirements into a grounding brief and stub the PRD").option("-f, --from <file>", "extract from an existing BRD/requirements document instead of interactive Q&A").action((feature, options) => smelt(feature, { from: options.from }));
|
|
7535
7597
|
program2.command("schema <feature>").description("Scaffold schema.dbml (requires prd.md)").action(makeScaffoldCommand({ file: SPEC_FILES.schema, label: "schema" }));
|
|
7536
7598
|
program2.command("contract <feature>").description("Scaffold api-contract.md (requires prd.md)").action(makeScaffoldCommand({ file: SPEC_FILES.contract, label: "contract" }));
|
|
7537
7599
|
program2.command("tasks <feature>").description("Scaffold tasks.md WBS (requires prd.md)").action(makeScaffoldCommand({ file: SPEC_FILES.tasks, label: "tasks" }));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rijalpermana/spec-forge",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Spec-driven development CLI: scaffolds specs, schemas, API contracts, task lists, and test cases; bridges into Claude Code as slash commands.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Project Rules & Conventions
|
|
2
|
+
|
|
3
|
+
> Read by the AI before drafting any spec (schema, contract, tasks, testcases)
|
|
4
|
+
> through `forge`. Rules here **override** forge's built-in defaults. Fill every
|
|
5
|
+
> `[TODO]`, delete sections that don't apply, and keep entries factual and
|
|
6
|
+
> enforceable — the AI follows this literally.
|
|
7
|
+
|
|
8
|
+
## Tech stack
|
|
9
|
+
|
|
10
|
+
- Language / runtime: [TODO]
|
|
11
|
+
- Framework: [TODO]
|
|
12
|
+
- Database / ORM: [TODO]
|
|
13
|
+
- Other key libraries: [TODO]
|
|
14
|
+
|
|
15
|
+
## Naming conventions
|
|
16
|
+
|
|
17
|
+
- Folders & files: [TODO — e.g. kebab-case]
|
|
18
|
+
- Classes / types: [TODO — e.g. PascalCase]
|
|
19
|
+
- Variables & functions: [TODO — e.g. camelCase]
|
|
20
|
+
- Database columns: [TODO — e.g. snake_case]
|
|
21
|
+
- API endpoints / paths: [TODO — e.g. kebab-case, `/v1/...`]
|
|
22
|
+
- Forbidden: [TODO — e.g. no underscores in file/folder names]
|
|
23
|
+
|
|
24
|
+
## Project & module structure
|
|
25
|
+
|
|
26
|
+
[TODO — describe the folder layout a new feature/module must follow, e.g.
|
|
27
|
+
`dto/`, `entities/`, `*.controller.ts`, `*.service.ts`, `*.repository.ts`,
|
|
28
|
+
`*.module.ts`. The AI should scaffold new work to match this.]
|
|
29
|
+
|
|
30
|
+
## API design
|
|
31
|
+
|
|
32
|
+
- Versioning: [TODO — e.g. URI versioning `/v1/`, `/v2/`]
|
|
33
|
+
- Auth: [TODO — e.g. global guard, how to opt an endpoint out]
|
|
34
|
+
- Response envelope: [TODO — paste the exact success shape the API returns]
|
|
35
|
+
- Error shape & status: [TODO — how errors are formatted and what HTTP status is used]
|
|
36
|
+
- Pagination: [TODO — shape of paginated responses]
|
|
37
|
+
|
|
38
|
+
## Data & schema
|
|
39
|
+
|
|
40
|
+
- Audit/base fields every entity carries: [TODO — e.g. created_by, created_date, ...]
|
|
41
|
+
- Multi-tenancy / scoping: [TODO — or "n/a"]
|
|
42
|
+
- Type conventions: [TODO]
|
|
43
|
+
|
|
44
|
+
## Validation
|
|
45
|
+
|
|
46
|
+
[TODO — e.g. all inputs validated with a schema/validator; every field documented with an example]
|
|
47
|
+
|
|
48
|
+
## Security
|
|
49
|
+
|
|
50
|
+
[TODO — e.g. secrets from env only; encryption standard; required headers; rate limiting]
|
|
51
|
+
|
|
52
|
+
## Testing
|
|
53
|
+
|
|
54
|
+
- Framework: [TODO]
|
|
55
|
+
- Coverage threshold: [TODO — e.g. 80% branches/functions/lines/statements, build fails below]
|
|
56
|
+
- Test file convention: [TODO — e.g. `*.spec.ts` beside the file under test]
|
|
57
|
+
- Minimum per unit: [TODO — e.g. 1 success case + 1 error case]
|
|
58
|
+
|
|
59
|
+
## Documentation
|
|
60
|
+
|
|
61
|
+
[TODO — e.g. OpenAPI/Swagger required; every endpoint and DTO documented with examples]
|
|
62
|
+
|
|
63
|
+
## Tooling
|
|
64
|
+
|
|
65
|
+
- Lint: [TODO]
|
|
66
|
+
- Format: [TODO]
|
|
67
|
+
- Pre-commit checks: [TODO]
|
|
68
|
+
|
|
69
|
+
## Out of bounds
|
|
70
|
+
|
|
71
|
+
[TODO — anything the AI must never do in this codebase]
|