@warpgogol/forge 2.8.0 → 2.8.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/AGENTS.md +1 -1
- package/README.md +23 -13
- package/README.uk.md +19 -12
- package/dist/os/session/handlers/validate.d.ts.map +1 -1
- package/dist/os/session/handlers/validate.js +23 -1
- package/dist/os/session/handlers/validate.js.map +1 -1
- package/dist/os/session/types.d.ts +31 -1
- package/dist/os/session/types.d.ts.map +1 -1
- package/dist/os/session/types.js +7 -0
- package/dist/os/session/types.js.map +1 -1
- package/os/adr/adr.module.ts +158 -0
- package/os/adr/frontmatter-io.ts +80 -0
- package/os/adr/handlers/archive.ts +244 -0
- package/os/adr/handlers/implement-stamp.ts +331 -0
- package/os/adr/handlers/list-create.ts +207 -0
- package/os/adr/handlers/validate.test.ts +232 -0
- package/os/adr/handlers/validate.ts +429 -0
- package/os/adr/index.ts +33 -0
- package/os/adr/types.ts +141 -0
- package/os/audit/audit.module.ts +48 -0
- package/os/audit/frontmatter-io.ts +84 -0
- package/os/audit/handlers/archive.ts +239 -0
- package/os/audit/index.ts +23 -0
- package/os/audit/types.ts +29 -0
- package/os/compass/compass.module.ts +186 -0
- package/os/compass/handlers/compass-audit-handler.ts +382 -0
- package/os/compass/handlers/compass-change-summary-handler.ts +272 -0
- package/os/compass/handlers/compass-inventory-handler.ts +294 -0
- package/os/compass/handlers/compass-inventory.ts +521 -0
- package/os/compass/handlers/git-revision.ts +137 -0
- package/os/compass/handlers/resolve-scan-root-workpiece.test.ts +65 -0
- package/os/compass/handlers/resolve-scan-root.ts +79 -0
- package/os/compass/index.ts +33 -0
- package/os/core/core.module.ts +948 -0
- package/os/core/handlers/assets-check.ts +122 -0
- package/os/core/handlers/assets-helpers.ts +174 -0
- package/os/core/handlers/assets-list.ts +99 -0
- package/os/core/handlers/build.ts +155 -0
- package/os/core/handlers/determinism-check.ts +354 -0
- package/os/core/handlers/dev.ts +127 -0
- package/os/core/handlers/invariant-engine.test.ts +681 -0
- package/os/core/handlers/knowledge-compact.ts +248 -0
- package/os/core/handlers/lifecycle-handlers.test.ts +524 -0
- package/os/core/handlers/note-frontmatter-validate.test.ts +93 -0
- package/os/core/handlers/note-link-validate.test.ts +117 -0
- package/os/core/handlers/note-orphan-detect.test.ts +94 -0
- package/os/core/handlers/package-health.test.ts +226 -0
- package/os/core/handlers/package-health.ts +231 -0
- package/os/core/handlers/pinned-check.ts +200 -0
- package/os/core/handlers/pinned-init.ts +333 -0
- package/os/core/handlers/pinned-types.ts +50 -0
- package/os/core/handlers/pinned-validate.ts +301 -0
- package/os/core/handlers/profile-resolve.ts +92 -0
- package/os/core/handlers/release-prepare.ts +294 -0
- package/os/core/handlers/release-publish.ts +211 -0
- package/os/core/handlers/validate.ts +262 -0
- package/os/core/handlers/workspace-deps.ts +55 -0
- package/os/core/index.ts +13 -0
- package/os/exploration/exploration.module.ts +90 -0
- package/os/exploration/frontmatter-io.ts +77 -0
- package/os/exploration/handlers/archive.ts +159 -0
- package/os/exploration/handlers/list.ts +77 -0
- package/os/exploration/handlers/show.ts +107 -0
- package/os/exploration/index.ts +25 -0
- package/os/exploration/types.ts +65 -0
- package/os/mission/handlers/archive.test.ts +391 -0
- package/os/mission/handlers/archive.ts +453 -0
- package/os/mission/index.ts +1 -0
- package/os/mission/mission.module.ts +48 -0
- package/os/mission/types.ts +41 -0
- package/os/naming/index.ts +14 -0
- package/os/naming/naming-convention.test.ts +163 -0
- package/os/naming/naming-convention.ts +378 -0
- package/os/naming/naming.module.ts +36 -0
- package/os/notes/index.ts +10 -0
- package/os/notes/notes.module.ts +109 -0
- package/os/plan/frontmatter-io.ts +82 -0
- package/os/plan/handlers/archive.ts +238 -0
- package/os/plan/index.ts +23 -0
- package/os/plan/plan.module.ts +48 -0
- package/os/plan/types.ts +29 -0
- package/os/program/discovery.ts +294 -0
- package/os/program/handlers/complete.ts +419 -0
- package/os/program/handlers/lease.ts +433 -0
- package/os/program/handlers/seal.ts +332 -0
- package/os/program/handlers/validate.ts +213 -0
- package/os/program/lease.ts +156 -0
- package/os/program/program.module.ts +212 -0
- package/os/program/schemas.ts +218 -0
- package/os/program/state.ts +264 -0
- package/os/rfc/acceptance.ts +344 -0
- package/os/rfc/decision-log.ts +310 -0
- package/os/rfc/dna-trace.ts +280 -0
- package/os/rfc/frontmatter-io.test.ts +132 -0
- package/os/rfc/frontmatter-io.ts +130 -0
- package/os/rfc/handlers/archive.ts +300 -0
- package/os/rfc/handlers/check.ts +238 -0
- package/os/rfc/handlers/implement-stamp.ts +515 -0
- package/os/rfc/handlers/index-graph.ts +237 -0
- package/os/rfc/handlers/lifecycle.test.ts +127 -0
- package/os/rfc/handlers/lifecycle.ts +249 -0
- package/os/rfc/handlers/list-create.ts +296 -0
- package/os/rfc/handlers/pipeline-status.ts +173 -0
- package/os/rfc/handlers/shared.ts +121 -0
- package/os/rfc/handlers/supersede-propose.ts +241 -0
- package/os/rfc/handlers/validate-rules.test.ts +891 -0
- package/os/rfc/handlers/validate-rules.ts +984 -0
- package/os/rfc/handlers/validate.ts +166 -0
- package/os/rfc/handlers.ts +19 -0
- package/os/rfc/index.ts +97 -0
- package/os/rfc/rfc.module.ts +416 -0
- package/os/rfc/types.ts +596 -0
- package/os/rfc/verification-evidence.ts +244 -0
- package/os/session/atif-parser.ts +166 -0
- package/os/session/frontmatter-io.ts +163 -0
- package/os/session/handlers/archive.ts +288 -0
- package/os/session/handlers/list.ts +174 -0
- package/os/session/handlers/save.ts +348 -0
- package/os/session/handlers/validate.ts +250 -0
- package/os/session/index.ts +50 -0
- package/os/session/session.module.ts +126 -0
- package/os/session/types.ts +203 -0
- package/os/spec/live-spec-list-show-validate.test.ts +257 -0
- package/os/spec/live-spec-list.ts +78 -0
- package/os/spec/live-spec-merge.test.ts +261 -0
- package/os/spec/live-spec-merge.ts +413 -0
- package/os/spec/live-spec-show.ts +117 -0
- package/os/spec/live-spec-types.ts +93 -0
- package/os/spec/live-spec-validate.ts +170 -0
- package/os/spec/spec-materialize.ts +390 -0
- package/os/spec/spec-schema.ts +174 -0
- package/os/spec/spec-status.ts +284 -0
- package/os/spec/spec-validate.ts +513 -0
- package/os/spec/spec.module.ts +135 -0
- package/os/werkstatt/handlers/lock.ts +168 -0
- package/os/werkstatt/handlers/schema.ts +47 -0
- package/os/werkstatt/handlers/werkstatt-lock-recover.ts +175 -0
- package/os/werkstatt/handlers/werkstatt-lock-status.ts +73 -0
- package/os/werkstatt/handlers/werkstatt-operation-validate.ts +104 -0
- package/os/werkstatt/index.ts +39 -0
- package/os/werkstatt/werkstatt.module.ts +62 -0
- package/os/workflow/handlers.ts +304 -0
- package/os/workflow/index.ts +28 -0
- package/os/workflow/types.ts +99 -0
- package/os/workflow/workflow.module.ts +54 -0
- package/package.json +3 -3
- package/profiles/godot-csharp.yaml +4 -4
- package/profiles/knowledge-typescript-turborepo.yaml +110 -0
- package/profiles/phaser-turborepo.yaml +5 -5
- package/skills/_shared/fo-session-summary.md +132 -16
- package/skills/fo/fo-add-tests/pbt-guide.md +13 -13
- package/skills/fo/fo-doc-audit/SKILL.md +6 -6
- package/skills/fo/fo-handoff/SKILL.md +5 -0
- package/skills/fo/fo-idea/SKILL.md +1 -1
- package/skills/fo/fo-review/SKILL.md +1 -1
- package/skills/fo/fo-session-retro/SKILL.md +8 -0
- package/skills/fo/fo-session-save/SKILL.md +8 -0
- package/src/cli-output.ts +59 -0
- package/src/config/__tests__/resolve-terminology.test.ts +45 -0
- package/src/config/forge-config.ts +524 -0
- package/src/forge-module.ts +39 -0
- package/src/index.ts +172 -0
- package/src/knowledge/__tests__/promote.test.ts +219 -0
- package/src/knowledge/__tests__/serialize.test.ts +192 -0
- package/src/knowledge/budgets.ts +202 -0
- package/src/knowledge/compact.ts +405 -0
- package/src/knowledge/index.ts +53 -0
- package/src/knowledge/parse.ts +227 -0
- package/src/knowledge/promote.ts +156 -0
- package/src/knowledge/schema.ts +112 -0
- package/src/knowledge/serialize.ts +72 -0
- package/src/migration-adapters/__tests__/ignored-files.test.ts +51 -0
- package/src/migration-adapters/__tests__/registry.test.ts +131 -0
- package/src/migration-adapters/__tests__/types.test.ts +86 -0
- package/src/migration-adapters/git-utils.ts +71 -0
- package/src/migration-adapters/ignored-files.ts +221 -0
- package/src/migration-adapters/index.ts +21 -0
- package/src/migration-adapters/node-typescript-pnpm/index.ts +189 -0
- package/src/migration-adapters/phaser-pnpm/index.ts +189 -0
- package/src/migration-adapters/registry.ts +61 -0
- package/src/migration-adapters/types.ts +68 -0
- package/src/onboarding/__tests__/workspace-discovery.test.ts +134 -0
- package/src/onboarding/agents-generate.ts +443 -0
- package/src/onboarding/create.ts +427 -0
- package/src/onboarding/doctor.ts +1297 -0
- package/src/onboarding/init.ts +337 -0
- package/src/onboarding/invariant-engine.ts +448 -0
- package/src/onboarding/memory-scaffold.ts +189 -0
- package/src/onboarding/nested-agents-generate.ts +93 -0
- package/src/onboarding/nested-agents-templates.ts +233 -0
- package/src/onboarding/profile-validate.ts +139 -0
- package/src/onboarding/scaffold-project.ts +299 -0
- package/src/onboarding/scaffold.ts +143 -0
- package/src/onboarding/upgrade.ts +558 -0
- package/src/onboarding/workspace-discovery.ts +167 -0
- package/src/profiles/__tests__/profile-schema.test.ts +228 -0
- package/src/profiles/__tests__/stack-profile.test.ts +190 -0
- package/src/profiles/__tests__/terminology-utils.test.ts +71 -0
- package/src/profiles/profile-schema.ts +350 -0
- package/src/profiles/stack-profile.ts +188 -0
- package/src/profiles/terminology-utils.ts +36 -0
- package/src/registry.ts +185 -0
- package/src/skill-schema.ts +37 -0
- package/src/tests/acceptance-criteria.test.ts +99 -0
- package/src/tests/adr-implement-stamp.test.ts +368 -0
- package/src/tests/agents-generate-domain.test.ts +317 -0
- package/src/tests/agents-generate.test.ts +458 -0
- package/src/tests/bindings-schema.test.ts +314 -0
- package/src/tests/budgets.test.ts +213 -0
- package/src/tests/cli-output.test.ts +196 -0
- package/src/tests/compact.test.ts +796 -0
- package/src/tests/create.test.ts +289 -0
- package/src/tests/doctor-autonomy.test.ts +98 -0
- package/src/tests/doctor-bindings.test.ts +286 -0
- package/src/tests/doctor-domain.test.ts +113 -0
- package/src/tests/exploration-handlers.test.ts +256 -0
- package/src/tests/fixtures/agents-generate-business-before.txt +288 -0
- package/src/tests/forge-config.test.ts +420 -0
- package/src/tests/fs-atomic.test.ts +96 -0
- package/src/tests/fs.test.ts +103 -0
- package/src/tests/generated-marker.test.ts +161 -0
- package/src/tests/hash.test.ts +37 -0
- package/src/tests/implement-stamp.test.ts +505 -0
- package/src/tests/init-bindings.test.ts +183 -0
- package/src/tests/knowledge-parse.test.ts +307 -0
- package/src/tests/knowledge-pbt.test.ts +164 -0
- package/src/tests/memory-scaffold.test.ts +192 -0
- package/src/tests/migration-adapters.test.ts +480 -0
- package/src/tests/package-files.test.ts +54 -0
- package/src/tests/pinned-check.test.ts +194 -0
- package/src/tests/pinned-init.test.ts +126 -0
- package/src/tests/profile-schema.test.ts +194 -0
- package/src/tests/profile-validate.test.ts +120 -0
- package/src/tests/program-lease.test.ts +245 -0
- package/src/tests/program-paths-extended.test.ts +117 -0
- package/src/tests/program-paths.test.ts +98 -0
- package/src/tests/program-property.test.ts +139 -0
- package/src/tests/program-schemas.test.ts +445 -0
- package/src/tests/program-spec-node.test.ts +250 -0
- package/src/tests/promote.test.ts +481 -0
- package/src/tests/registry.test.ts +73 -0
- package/src/tests/scaffold-project.test.ts +150 -0
- package/src/tests/session-handlers.test.ts +503 -0
- package/src/tests/session-pbt.test.ts +190 -0
- package/src/tests/skill-schema.test.ts +146 -0
- package/src/tests/skill-validate-knowledge.test.ts +26 -0
- package/src/tests/skill-validate.test.ts +254 -0
- package/src/tests/stack-profile.test.ts +221 -0
- package/src/tests/string-utils.test.ts +42 -0
- package/src/tests/upgrade.test.ts +428 -0
- package/src/tests/werkstatt-lock.test.ts +343 -0
- package/src/tests/workspace-discovery-domain.test.ts +112 -0
- package/src/tests/workspace-discovery.test.ts +135 -0
- package/src/types.ts +221 -0
- package/src/utils/__tests__/fs-idempotent.test.ts +73 -0
- package/src/utils/__tests__/fs-trash.test.ts +45 -0
- package/src/utils/fs-atomic.ts +93 -0
- package/src/utils/fs-idempotent.ts +41 -0
- package/src/utils/fs-trash-sync.ts +37 -0
- package/src/utils/fs-trash.ts +24 -0
- package/src/utils/fs.ts +74 -0
- package/src/utils/generated-marker.ts +166 -0
- package/src/utils/hash.ts +19 -0
- package/src/utils/index.ts +29 -0
- package/src/utils/string-utils.ts +19 -0
- package/src/validators/__tests__/note-orphan-detect.test.ts +94 -0
- package/src/validators/note-frontmatter-validate.ts +142 -0
- package/src/validators/note-link-validate.ts +145 -0
- package/src/validators/note-orphan-detect.ts +146 -0
- package/src/validators/port-validate.ts +97 -0
- package/src/validators/skill-validate.ts +831 -0
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>forge.create — in-place project scaffolding. Composes forge.scaffold + forge.init + package-manager post-processing into one command. Scaffolds directly into the current directory (no subdirectory).</purpose>
|
|
4
|
+
<non-goals>
|
|
5
|
+
<item>Do not change forge.init or forge.scaffold contracts — forge.create delegates to them.</item>
|
|
6
|
+
<item>Do not add interactive prompts — all configuration is via flags and positional args.</item>
|
|
7
|
+
<item>Do not import from @warpgogol/* — this module is portable.</item>
|
|
8
|
+
<item>Do not create subdirectories — --in-place is the only mode (RFC-0877).</item>
|
|
9
|
+
</non-goals>
|
|
10
|
+
</MODULE_CONTRACT>
|
|
11
|
+
<CHANGE_SUMMARY>
|
|
12
|
+
<item>RFC-0544: initial forge.create handler composing scaffold + init.</item>
|
|
13
|
+
<item>RFC-0544 fix: replace double cast on runInit return with proper InitResult typing.</item>
|
|
14
|
+
<item>RFC-0548: auto-run forge.agents.generate after init, update nextSteps to remove manual AGENTS.md step.</item>
|
|
15
|
+
<item>RFC-0550: write NEXT_STEPS.md into project root with creator-facing guidance (greenfield vs transplant via LLM).</item>
|
|
16
|
+
<item>RFC-0640: load profile domain fields and pass them to runInit for domain-aware bootstrapping.</item>
|
|
17
|
+
<item>RFC-0643: pass profileId to runInit so forge.yaml gets a `profile` field.</item>
|
|
18
|
+
<item>RFC-0664: scaffold memory layer (.agents/memory/) after init.</item>
|
|
19
|
+
<item>RFC-0877: in-place mode only — --in-place flag required, no subdirectory creation, name derived from folder, strict empty-directory check (only .git/ tolerated).</item>
|
|
20
|
+
</CHANGE_SUMMARY>
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import fs from "node:fs";
|
|
24
|
+
import path from "node:path";
|
|
25
|
+
import { stringify as stringifyYaml } from "yaml";
|
|
26
|
+
import { runScaffoldProject } from "./scaffold-project.ts";
|
|
27
|
+
import { runInit, type InitResult, type InitDomainFields } from "./init.ts";
|
|
28
|
+
import { runAgentsGenerate } from "./agents-generate.ts";
|
|
29
|
+
import { scaffoldMemoryLayer } from "./memory-scaffold.ts";
|
|
30
|
+
import {
|
|
31
|
+
loadForgeConfig,
|
|
32
|
+
resolveForgeRoot,
|
|
33
|
+
resolvePackageManager,
|
|
34
|
+
applyCliBindingDefaults,
|
|
35
|
+
} from "../config/forge-config.ts";
|
|
36
|
+
import { listStackProfiles } from "../profiles/stack-profile.ts";
|
|
37
|
+
import type {
|
|
38
|
+
ForgeCommandInput,
|
|
39
|
+
ForgeCommandResult,
|
|
40
|
+
ForgeNextStep,
|
|
41
|
+
ForgeRuntimeContext,
|
|
42
|
+
} from "../types.ts";
|
|
43
|
+
|
|
44
|
+
interface CreateCommandResult {
|
|
45
|
+
command: "forge.create";
|
|
46
|
+
status: "pass" | "fail";
|
|
47
|
+
projectDir: string;
|
|
48
|
+
profile: string;
|
|
49
|
+
filesCreated: string[];
|
|
50
|
+
errors: string[];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* RFC-0640: Load domain fields from the selected profile.
|
|
55
|
+
* Returns register, domain, terminology, and semanticBindings derived from profile.artifacts[].
|
|
56
|
+
* When the profile has no domain fields, returns empty object (fallback to current behavior).
|
|
57
|
+
*/
|
|
58
|
+
function loadProfileDomainFields(profileId: string, forgeRoot: string): InitDomainFields {
|
|
59
|
+
try {
|
|
60
|
+
const profiles = listStackProfiles(forgeRoot);
|
|
61
|
+
const profile = profiles.find((p) => p.id === profileId);
|
|
62
|
+
if (!profile) return {};
|
|
63
|
+
|
|
64
|
+
const fields: InitDomainFields = {};
|
|
65
|
+
fields.profileId = profileId;
|
|
66
|
+
if (profile.register) {
|
|
67
|
+
fields.register = profile.register;
|
|
68
|
+
}
|
|
69
|
+
if (profile.domain) {
|
|
70
|
+
fields.domain = profile.domain;
|
|
71
|
+
}
|
|
72
|
+
if (profile.terminology) {
|
|
73
|
+
fields.terminology = profile.terminology;
|
|
74
|
+
}
|
|
75
|
+
// Derive semantic binding defaults from profile.artifacts[]
|
|
76
|
+
if (profile.artifacts && profile.artifacts.length > 0) {
|
|
77
|
+
const semanticBindings: Record<string, string | null> = {};
|
|
78
|
+
for (const artifact of profile.artifacts) {
|
|
79
|
+
if (artifact.produce?.command) {
|
|
80
|
+
semanticBindings.produce = artifact.produce.command;
|
|
81
|
+
}
|
|
82
|
+
if (artifact.validate?.command) {
|
|
83
|
+
semanticBindings.validate = artifact.validate.command;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (Object.keys(semanticBindings).length > 0) {
|
|
87
|
+
fields.semanticBindings = semanticBindings;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return fields;
|
|
91
|
+
} catch {
|
|
92
|
+
return {};
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Convert a string to kebab-case.
|
|
98
|
+
* Handles camelCase, PascalCase, snake_case, spaces, and mixed separators.
|
|
99
|
+
*/
|
|
100
|
+
function toKebabCase(s: string): string {
|
|
101
|
+
return s
|
|
102
|
+
.replace(/([a-z])([A-Z])/g, "$1-$2")
|
|
103
|
+
.replace(/[_\s]+/g, "-")
|
|
104
|
+
.replace(/[^a-zA-Z0-9-]/g, "")
|
|
105
|
+
.toLowerCase()
|
|
106
|
+
.replace(/^-+|-+$/g, "");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Directories that are allowed in an otherwise empty target directory.
|
|
111
|
+
* Only these are tolerated when checking for emptiness.
|
|
112
|
+
*/
|
|
113
|
+
const ALLOWED_EXISTING_DIRS = new Set([".git"]);
|
|
114
|
+
|
|
115
|
+
export async function runCreate(
|
|
116
|
+
input: ForgeCommandInput,
|
|
117
|
+
context: ForgeRuntimeContext,
|
|
118
|
+
): Promise<ForgeCommandResult<CreateCommandResult>> {
|
|
119
|
+
const { logger, outputFormat } = context;
|
|
120
|
+
const inPlace = input.flags["in-place"] as boolean | undefined;
|
|
121
|
+
const nameOverride = input.flags["name"] as string | undefined;
|
|
122
|
+
const profile = input.flags["profile"] as string | undefined;
|
|
123
|
+
const packageManager = (input.flags["package-manager"] as string | undefined) ?? "pnpm";
|
|
124
|
+
const template = input.flags["template"] as string | undefined;
|
|
125
|
+
|
|
126
|
+
const errors: string[] = [];
|
|
127
|
+
|
|
128
|
+
const passNextSteps: ForgeNextStep[] = [
|
|
129
|
+
{ action: "Run /forge-bootstrap immediately to configure language, register, and stack bindings", kind: "required" },
|
|
130
|
+
];
|
|
131
|
+
const failNextSteps: ForgeNextStep[] = [
|
|
132
|
+
{ action: "Fix the errors above and re-run forge.create", kind: "required" },
|
|
133
|
+
];
|
|
134
|
+
|
|
135
|
+
// 1. --in-place is required (RFC-0877)
|
|
136
|
+
if (!inPlace) {
|
|
137
|
+
const msg = "Missing required flag: --in-place";
|
|
138
|
+
errors.push(msg);
|
|
139
|
+
if (outputFormat === "pretty") {
|
|
140
|
+
logger.error(msg);
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
data: { command: "forge.create", status: "fail", projectDir: "", profile: profile ?? "", filesCreated: [], errors },
|
|
144
|
+
nextSteps: failNextSteps,
|
|
145
|
+
exitCode: 1,
|
|
146
|
+
summary: "forge.create: failed — missing --in-place flag",
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// 2. --profile is required (RFC-0877)
|
|
151
|
+
if (!profile) {
|
|
152
|
+
const msg = "Missing required flag: --profile <profile-id>";
|
|
153
|
+
errors.push(msg);
|
|
154
|
+
if (outputFormat === "pretty") {
|
|
155
|
+
logger.error(msg);
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
data: { command: "forge.create", status: "fail", projectDir: "", profile: "", filesCreated: [], errors },
|
|
159
|
+
nextSteps: failNextSteps,
|
|
160
|
+
exitCode: 1,
|
|
161
|
+
summary: "forge.create: failed — missing --profile flag",
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// 3. Resolve target directory — in-place mode uses workspaceRoot directly
|
|
166
|
+
const targetDir = path.resolve(context.workspaceRoot);
|
|
167
|
+
|
|
168
|
+
// 4. Derive project name from folder name if not provided
|
|
169
|
+
let name: string;
|
|
170
|
+
if (nameOverride) {
|
|
171
|
+
name = nameOverride;
|
|
172
|
+
} else {
|
|
173
|
+
const folderName = path.basename(targetDir);
|
|
174
|
+
name = toKebabCase(folderName);
|
|
175
|
+
if (!name) {
|
|
176
|
+
const msg = `Cannot derive project name from folder "${folderName}" — provide --name explicitly`;
|
|
177
|
+
errors.push(msg);
|
|
178
|
+
if (outputFormat === "pretty") {
|
|
179
|
+
logger.error(msg);
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
data: { command: "forge.create", status: "fail", projectDir: targetDir, profile, filesCreated: [], errors },
|
|
183
|
+
nextSteps: failNextSteps,
|
|
184
|
+
exitCode: 1,
|
|
185
|
+
summary: `forge.create: failed — ${msg}`,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// 5. Validate kebab-case
|
|
191
|
+
if (!/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/.test(name)) {
|
|
192
|
+
const msg = `Project name "${name}" is not kebab-case (expected ^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$)`;
|
|
193
|
+
errors.push(msg);
|
|
194
|
+
if (outputFormat === "pretty") {
|
|
195
|
+
logger.error(msg);
|
|
196
|
+
}
|
|
197
|
+
return {
|
|
198
|
+
data: { command: "forge.create", status: "fail", projectDir: targetDir, profile, filesCreated: [], errors },
|
|
199
|
+
nextSteps: failNextSteps,
|
|
200
|
+
exitCode: 1,
|
|
201
|
+
summary: `forge.create: failed — ${msg}`,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// 6. Strict empty-directory check (RFC-0877)
|
|
206
|
+
// The target directory must be empty (only .git/ is tolerated).
|
|
207
|
+
// If any other files or directories exist, refuse to scaffold.
|
|
208
|
+
let dirEntries: string[];
|
|
209
|
+
try {
|
|
210
|
+
dirEntries = fs.readdirSync(targetDir);
|
|
211
|
+
} catch {
|
|
212
|
+
dirEntries = [];
|
|
213
|
+
}
|
|
214
|
+
const unexpectedEntries = dirEntries.filter((e) => !ALLOWED_EXISTING_DIRS.has(e));
|
|
215
|
+
if (unexpectedEntries.length > 0) {
|
|
216
|
+
const msg = `Directory is not empty. Found: ${unexpectedEntries.slice(0, 10).join(", ")}${unexpectedEntries.length > 10 ? ` (and ${unexpectedEntries.length - 10} more)` : ""}. The target directory must be empty. Refusing to scaffold.`;
|
|
217
|
+
errors.push(msg);
|
|
218
|
+
if (outputFormat === "pretty") {
|
|
219
|
+
logger.error(msg);
|
|
220
|
+
}
|
|
221
|
+
return {
|
|
222
|
+
data: { command: "forge.create", status: "fail", projectDir: targetDir, profile, filesCreated: [], errors },
|
|
223
|
+
nextSteps: failNextSteps,
|
|
224
|
+
exitCode: 1,
|
|
225
|
+
summary: `forge.create: failed — ${msg}`,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// 7. Resolve forge root
|
|
230
|
+
let forgeRoot: string;
|
|
231
|
+
if (context.forgeRoot) {
|
|
232
|
+
forgeRoot = context.forgeRoot;
|
|
233
|
+
} else {
|
|
234
|
+
try {
|
|
235
|
+
forgeRoot = resolveForgeRoot(context.workspaceRoot);
|
|
236
|
+
} catch {
|
|
237
|
+
forgeRoot = context.workspaceRoot;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (outputFormat === "pretty") {
|
|
241
|
+
logger.info(`Using forge root: ${forgeRoot}`);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// 8. Build child context with workspaceRoot = targetDir and forgeRoot override
|
|
245
|
+
const childContext: ForgeRuntimeContext = {
|
|
246
|
+
...context,
|
|
247
|
+
workspaceRoot: targetDir,
|
|
248
|
+
forgeRoot,
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
// 9. Run forge.scaffold inside target dir
|
|
252
|
+
const scaffoldInput: ForgeCommandInput = {
|
|
253
|
+
argv: [],
|
|
254
|
+
flags: { profile, name, ...(template ? { template } : {}) },
|
|
255
|
+
};
|
|
256
|
+
const scaffoldResult = await runScaffoldProject(scaffoldInput, childContext);
|
|
257
|
+
if (scaffoldResult.exitCode !== 0) {
|
|
258
|
+
const scaffoldErrors = scaffoldResult.data?.errors ?? ["scaffold failed"];
|
|
259
|
+
return {
|
|
260
|
+
data: {
|
|
261
|
+
command: "forge.create",
|
|
262
|
+
status: "fail",
|
|
263
|
+
projectDir: targetDir,
|
|
264
|
+
profile,
|
|
265
|
+
filesCreated: scaffoldResult.data?.created ?? [],
|
|
266
|
+
errors: scaffoldErrors,
|
|
267
|
+
},
|
|
268
|
+
nextSteps: failNextSteps,
|
|
269
|
+
exitCode: 1,
|
|
270
|
+
summary: `forge.create: failed — scaffold step failed`,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// 10. Run forge.init inside target dir
|
|
275
|
+
// RFC-0640: load profile domain fields and pass them to runInit
|
|
276
|
+
const initDomainFields = loadProfileDomainFields(profile, forgeRoot);
|
|
277
|
+
const initInput: ForgeCommandInput = {
|
|
278
|
+
argv: [],
|
|
279
|
+
flags: {},
|
|
280
|
+
};
|
|
281
|
+
const initResult: InitResult = runInit(initInput, childContext, initDomainFields);
|
|
282
|
+
|
|
283
|
+
if (outputFormat === "pretty") {
|
|
284
|
+
logger.info(`Init: ${initResult.status} — ${initResult.created.length} created, ${initResult.skipped.length} skipped, ${initResult.errors.length} errors`);
|
|
285
|
+
if (initResult.errors.length > 0) {
|
|
286
|
+
for (const err of initResult.errors.slice(0, 3)) {
|
|
287
|
+
logger.warn(` init error: ${err}`);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// 10.5. Scaffold memory layer (RFC-0664) — runs regardless of init success
|
|
293
|
+
const memoryScaffoldResult = scaffoldMemoryLayer(targetDir);
|
|
294
|
+
|
|
295
|
+
if (initResult.status !== "pass") {
|
|
296
|
+
if (outputFormat === "pretty") {
|
|
297
|
+
logger.error(`Init failed with ${initResult.errors.length} error(s):`);
|
|
298
|
+
for (const err of initResult.errors.slice(0, 5)) {
|
|
299
|
+
logger.error(` ${err}`);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return {
|
|
303
|
+
data: {
|
|
304
|
+
command: "forge.create",
|
|
305
|
+
status: "fail",
|
|
306
|
+
projectDir: targetDir,
|
|
307
|
+
profile,
|
|
308
|
+
filesCreated: [...(scaffoldResult.data?.created ?? []), ...(initResult.created ?? []), ...memoryScaffoldResult.created],
|
|
309
|
+
errors: initResult.errors ?? ["init failed"],
|
|
310
|
+
},
|
|
311
|
+
nextSteps: failNextSteps,
|
|
312
|
+
exitCode: 1,
|
|
313
|
+
summary: `forge.create: failed — init step failed`,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// 11. Post-process forge.yaml if --package-manager differs from default
|
|
318
|
+
if (packageManager !== "pnpm") {
|
|
319
|
+
const forgeYamlPath = path.join(targetDir, "forge.yaml");
|
|
320
|
+
if (fs.existsSync(forgeYamlPath)) {
|
|
321
|
+
try {
|
|
322
|
+
const config = loadForgeConfig(targetDir);
|
|
323
|
+
const pm = resolvePackageManager(packageManager);
|
|
324
|
+
config.project.packageManager = pm;
|
|
325
|
+
if (config.bindings) {
|
|
326
|
+
config.bindings.commands = applyCliBindingDefaults(pm);
|
|
327
|
+
}
|
|
328
|
+
// RFC-0643: strip loaded profile object before serializing — forge.yaml stores profile id (string), not the full profile
|
|
329
|
+
const configToSerialize = { ...config } as unknown as Record<string, unknown>;
|
|
330
|
+
if (typeof configToSerialize["profile"] === "object") {
|
|
331
|
+
const profileObj = configToSerialize["profile"] as { id?: string };
|
|
332
|
+
configToSerialize["profile"] = profileObj.id ?? configToSerialize["profile"];
|
|
333
|
+
}
|
|
334
|
+
fs.writeFileSync(forgeYamlPath, stringifyYaml(configToSerialize), "utf8");
|
|
335
|
+
} catch {
|
|
336
|
+
// Post-processing is best-effort — init already wrote a valid config
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// 12. Auto-run forge.agents.generate (RFC-0548)
|
|
342
|
+
try {
|
|
343
|
+
const agentsInput: ForgeCommandInput = {
|
|
344
|
+
argv: [],
|
|
345
|
+
flags: {},
|
|
346
|
+
};
|
|
347
|
+
if (outputFormat === "pretty") {
|
|
348
|
+
logger.info("Generating AGENTS.md...");
|
|
349
|
+
}
|
|
350
|
+
const agentsResult = await runAgentsGenerate(agentsInput, childContext);
|
|
351
|
+
if (outputFormat === "pretty") {
|
|
352
|
+
logger.info(`Agents.generate: ${agentsResult.exitCode === 0 ? "ok" : "failed"} — ${(agentsResult.data?.generated ?? []).length} generated, ${(agentsResult.data?.errors ?? []).length} errors`);
|
|
353
|
+
}
|
|
354
|
+
if (agentsResult.exitCode !== 0 && outputFormat === "pretty") {
|
|
355
|
+
logger.warn("forge.agents.generate failed — AGENTS.md not generated. Run 'forge agents generate' manually after fixing the issue.");
|
|
356
|
+
for (const err of (agentsResult.data?.errors ?? []).slice(0, 3)) {
|
|
357
|
+
logger.warn(` ${err}`);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
} catch (err) {
|
|
361
|
+
if (outputFormat === "pretty") {
|
|
362
|
+
logger.warn("forge.agents.generate failed — AGENTS.md not generated. Run 'forge agents generate' manually after fixing the issue.");
|
|
363
|
+
logger.warn(` ${(err as Error).message}`);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// 13. Write NEXT_STEPS.md (RFC-0550)
|
|
368
|
+
const nextStepsPath = path.join(targetDir, "NEXT_STEPS.md");
|
|
369
|
+
const nextStepsContent = `# Next Steps
|
|
370
|
+
|
|
371
|
+
Your Forge project is ready. The next step is mandatory: run \`/forge-bootstrap\` to configure your project interactively.
|
|
372
|
+
|
|
373
|
+
## Step 1: Configure your project (required)
|
|
374
|
+
|
|
375
|
+
Run \`/forge-bootstrap\` now. It will ask you:
|
|
376
|
+
|
|
377
|
+
- Which language the AI should communicate with you in
|
|
378
|
+
- Which language project documentation should be written in
|
|
379
|
+
- Whether you prefer a business or creative working style
|
|
380
|
+
- Your name and how you want to be addressed
|
|
381
|
+
- Your stack configuration (or migrate an existing project)
|
|
382
|
+
|
|
383
|
+
This step is required before any work begins — it sets up language preferences, communication style, and project bindings.
|
|
384
|
+
|
|
385
|
+
## Step 2: Start creating
|
|
386
|
+
|
|
387
|
+
After configuration, tell the AI agent what you want to build. Describe your idea in your own words — a website, a game, a blog, a tool — and the system will set everything up and start creating with you.
|
|
388
|
+
|
|
389
|
+
## Alternative: Bring your existing project
|
|
390
|
+
|
|
391
|
+
If you already have a project elsewhere and want to move it into Forge, tell the AI agent:
|
|
392
|
+
|
|
393
|
+
> I want to bring my existing project into Forge.
|
|
394
|
+
|
|
395
|
+
The system will guide you through the process — it detects your project type, migrates the code (including \`.env\` and git-ignored files), optionally transfers git history, and verifies the build.
|
|
396
|
+
`;
|
|
397
|
+
fs.writeFileSync(nextStepsPath, nextStepsContent, "utf8");
|
|
398
|
+
|
|
399
|
+
// 14. Collect filesCreated
|
|
400
|
+
const filesCreated = [
|
|
401
|
+
...(scaffoldResult.data?.created ?? []),
|
|
402
|
+
...(initResult.created ?? []),
|
|
403
|
+
...memoryScaffoldResult.created,
|
|
404
|
+
"NEXT_STEPS.md",
|
|
405
|
+
];
|
|
406
|
+
|
|
407
|
+
if (outputFormat === "pretty") {
|
|
408
|
+
logger.success(`Created project "${name}" in ${targetDir}`);
|
|
409
|
+
for (const f of filesCreated) {
|
|
410
|
+
logger.info(` created: ${f}`);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
return {
|
|
415
|
+
data: {
|
|
416
|
+
command: "forge.create",
|
|
417
|
+
status: "pass",
|
|
418
|
+
projectDir: targetDir,
|
|
419
|
+
profile,
|
|
420
|
+
filesCreated,
|
|
421
|
+
errors: [],
|
|
422
|
+
},
|
|
423
|
+
nextSteps: passNextSteps,
|
|
424
|
+
exitCode: 0,
|
|
425
|
+
summary: `forge.create: OK — created ${name} with ${filesCreated.length} files`,
|
|
426
|
+
};
|
|
427
|
+
}
|