@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,131 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { getAdapters, detectAdapter, detectAdapters } from "../registry.ts";
|
|
3
|
+
import { nodeTypescriptPnpmAdapter } from "../node-typescript-pnpm/index.ts";
|
|
4
|
+
import { phaserPnpmAdapter } from "../phaser-pnpm/index.ts";
|
|
5
|
+
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { tmpdir } from "node:os";
|
|
8
|
+
|
|
9
|
+
let tempDir: string;
|
|
10
|
+
|
|
11
|
+
beforeEach(async () => {
|
|
12
|
+
tempDir = await mkdtemp(join(tmpdir(), "forge-registry-"));
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
afterEach(async () => {
|
|
16
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe("getAdapters", () => {
|
|
20
|
+
it("returns built-in adapters", () => {
|
|
21
|
+
const adapters = getAdapters();
|
|
22
|
+
expect(adapters).toHaveLength(2);
|
|
23
|
+
const ids = adapters.map((a) => a.id);
|
|
24
|
+
expect(ids).toContain("node-typescript-pnpm");
|
|
25
|
+
expect(ids).toContain("phaser-pnpm");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("returns built-in adapters even with undefined config", () => {
|
|
29
|
+
const adapters = getAdapters(undefined);
|
|
30
|
+
expect(adapters).toHaveLength(2);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe("detectAdapter", () => {
|
|
35
|
+
it("detects node-typescript-pnpm when package.json + tsconfig.json + pnpm-lock.yaml exist", async () => {
|
|
36
|
+
await writeFile(join(tempDir, "package.json"), '{"name":"test"}', "utf8");
|
|
37
|
+
await writeFile(join(tempDir, "tsconfig.json"), "{}", "utf8");
|
|
38
|
+
await writeFile(join(tempDir, "pnpm-lock.yaml"), "", "utf8");
|
|
39
|
+
|
|
40
|
+
const adapter = detectAdapter(tempDir);
|
|
41
|
+
expect(adapter?.id).toBe("node-typescript-pnpm");
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("detects phaser-pnpm when phaser is in dependencies", async () => {
|
|
45
|
+
await writeFile(
|
|
46
|
+
join(tempDir, "package.json"),
|
|
47
|
+
'{"name":"game","dependencies":{"phaser":"^3.0.0"}}',
|
|
48
|
+
"utf8",
|
|
49
|
+
);
|
|
50
|
+
await writeFile(join(tempDir, "pnpm-lock.yaml"), "", "utf8");
|
|
51
|
+
|
|
52
|
+
const adapter = detectAdapter(tempDir);
|
|
53
|
+
expect(adapter?.id).toBe("phaser-pnpm");
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("returns null when no adapter matches", () => {
|
|
57
|
+
const adapter = detectAdapter(tempDir);
|
|
58
|
+
expect(adapter).toBeNull();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("returns first matching adapter when multiple match", async () => {
|
|
62
|
+
await writeFile(
|
|
63
|
+
join(tempDir, "package.json"),
|
|
64
|
+
'{"name":"game","dependencies":{"phaser":"^3.0.0"}}',
|
|
65
|
+
"utf8",
|
|
66
|
+
);
|
|
67
|
+
await writeFile(join(tempDir, "tsconfig.json"), "{}", "utf8");
|
|
68
|
+
await writeFile(join(tempDir, "pnpm-lock.yaml"), "", "utf8");
|
|
69
|
+
|
|
70
|
+
const adapter = detectAdapter(tempDir);
|
|
71
|
+
expect(adapter).toBeDefined();
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe("detectAdapters", () => {
|
|
76
|
+
it("returns all matching adapters", async () => {
|
|
77
|
+
await writeFile(
|
|
78
|
+
join(tempDir, "package.json"),
|
|
79
|
+
'{"name":"game","dependencies":{"phaser":"^3.0.0"}}',
|
|
80
|
+
"utf8",
|
|
81
|
+
);
|
|
82
|
+
await writeFile(join(tempDir, "tsconfig.json"), "{}", "utf8");
|
|
83
|
+
await writeFile(join(tempDir, "pnpm-lock.yaml"), "", "utf8");
|
|
84
|
+
|
|
85
|
+
const adapters = detectAdapters(tempDir);
|
|
86
|
+
expect(adapters.length).toBeGreaterThanOrEqual(1);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("returns empty array when nothing matches", () => {
|
|
90
|
+
expect(detectAdapters(tempDir)).toEqual([]);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe("built-in adapter analyze", () => {
|
|
95
|
+
it("nodeTypescriptPnpmAdapter.analyze derives bindings from scripts", async () => {
|
|
96
|
+
await writeFile(
|
|
97
|
+
join(tempDir, "package.json"),
|
|
98
|
+
JSON.stringify({
|
|
99
|
+
name: "@scope/my-app",
|
|
100
|
+
scripts: {
|
|
101
|
+
build: "tsc",
|
|
102
|
+
test: "vitest",
|
|
103
|
+
typecheck: "tsc --noEmit",
|
|
104
|
+
},
|
|
105
|
+
}),
|
|
106
|
+
"utf8",
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
const analysis = nodeTypescriptPnpmAdapter.analyze(tempDir);
|
|
110
|
+
expect(analysis.packageManager).toBe("pnpm");
|
|
111
|
+
expect(analysis.appName).toBe("my-app");
|
|
112
|
+
expect(analysis.bindings.test).toBe("vitest");
|
|
113
|
+
expect(analysis.bindings.scopedBuild).toBe("tsc");
|
|
114
|
+
expect(analysis.placement).toBe("apps");
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("phaserPnpmAdapter.analyze detects phaser stack", async () => {
|
|
118
|
+
await writeFile(
|
|
119
|
+
join(tempDir, "package.json"),
|
|
120
|
+
JSON.stringify({
|
|
121
|
+
name: "my-game",
|
|
122
|
+
dependencies: { phaser: "^3.0.0" },
|
|
123
|
+
}),
|
|
124
|
+
"utf8",
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
const analysis = phaserPnpmAdapter.analyze(tempDir);
|
|
128
|
+
expect(analysis.stack).toContain("phaser");
|
|
129
|
+
expect(analysis.appName).toBe("my-game");
|
|
130
|
+
});
|
|
131
|
+
});
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
FORGE_PROTECTED_PATHS,
|
|
4
|
+
DEFAULT_EXCLUDE_PATTERNS,
|
|
5
|
+
type MigrationAdapter,
|
|
6
|
+
type AdapterAnalysis,
|
|
7
|
+
type MigrationResult,
|
|
8
|
+
type Conflict,
|
|
9
|
+
} from "../types.ts";
|
|
10
|
+
|
|
11
|
+
describe("FORGE_PROTECTED_PATHS", () => {
|
|
12
|
+
it("contains forge.yaml", () => {
|
|
13
|
+
expect(FORGE_PROTECTED_PATHS).toContain("forge.yaml");
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("contains .agents", () => {
|
|
17
|
+
expect(FORGE_PROTECTED_PATHS).toContain(".agents");
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("contains docs/rfcs", () => {
|
|
21
|
+
expect(FORGE_PROTECTED_PATHS).toContain("docs/rfcs");
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("contains PREFERENCES.md", () => {
|
|
25
|
+
expect(FORGE_PROTECTED_PATHS).toContain("PREFERENCES.md");
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe("DEFAULT_EXCLUDE_PATTERNS", () => {
|
|
30
|
+
it("excludes node_modules", () => {
|
|
31
|
+
expect(DEFAULT_EXCLUDE_PATTERNS).toContain("node_modules");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("excludes dist", () => {
|
|
35
|
+
expect(DEFAULT_EXCLUDE_PATTERNS).toContain("dist");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("excludes .git", () => {
|
|
39
|
+
expect(DEFAULT_EXCLUDE_PATTERNS).toContain(".git");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("excludes .cache", () => {
|
|
43
|
+
expect(DEFAULT_EXCLUDE_PATTERNS).toContain(".cache");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("excludes .turbo", () => {
|
|
47
|
+
expect(DEFAULT_EXCLUDE_PATTERNS).toContain(".turbo");
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe("type contracts", () => {
|
|
52
|
+
it("MigrationAdapter interface has required methods", () => {
|
|
53
|
+
const adapter: MigrationAdapter = {
|
|
54
|
+
id: "test",
|
|
55
|
+
detect: () => true,
|
|
56
|
+
analyze: (): AdapterAnalysis => ({
|
|
57
|
+
stack: ["typescript"],
|
|
58
|
+
packageManager: "pnpm",
|
|
59
|
+
bindings: { typecheck: null, test: null, scopedBuild: null },
|
|
60
|
+
placement: "apps",
|
|
61
|
+
appName: "test",
|
|
62
|
+
excludePatterns: [],
|
|
63
|
+
gitHistory: false,
|
|
64
|
+
}),
|
|
65
|
+
migrate: (): MigrationResult => ({
|
|
66
|
+
filesCopied: [],
|
|
67
|
+
filesSkipped: [],
|
|
68
|
+
conflicts: [],
|
|
69
|
+
workspaceUpdated: false,
|
|
70
|
+
}),
|
|
71
|
+
postSetup: () => {},
|
|
72
|
+
};
|
|
73
|
+
expect(adapter.id).toBe("test");
|
|
74
|
+
expect(adapter.detect("/")).toBe(true);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("Conflict type has required fields", () => {
|
|
78
|
+
const conflict: Conflict = {
|
|
79
|
+
path: "src/file.ts",
|
|
80
|
+
sourceExists: true,
|
|
81
|
+
forgeExists: true,
|
|
82
|
+
resolution: "source-wins",
|
|
83
|
+
};
|
|
84
|
+
expect(conflict.resolution).toBe("source-wins");
|
|
85
|
+
});
|
|
86
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>Shared git utilities for migration adapters — git init and git history transfer via format-patch + git am (RFC-0547).</purpose>
|
|
4
|
+
<non-goals>
|
|
5
|
+
<item>Do not import from @warpgogol/* — this module is portable.</item>
|
|
6
|
+
<item>Do not implement adapter-specific logic — only shared git operations.</item>
|
|
7
|
+
</non-goals>
|
|
8
|
+
</MODULE_CONTRACT>
|
|
9
|
+
<CHANGE_SUMMARY>
|
|
10
|
+
<item>RFC-0547: extract shared postSetup git logic from duplicated adapter implementations.</item>
|
|
11
|
+
<item>Replace fs.rmSync with trashSync for patch directory cleanup (trash bin for LLM-initiated deletions).</item>
|
|
12
|
+
</CHANGE_SUMMARY>
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import fs from "node:fs";
|
|
16
|
+
import path from "node:path";
|
|
17
|
+
import { execFileSync } from "node:child_process";
|
|
18
|
+
import { trashSync } from "../utils/fs-trash-sync.ts";
|
|
19
|
+
import type { AdapterAnalysis } from "./types.ts";
|
|
20
|
+
|
|
21
|
+
export function runPostSetup(
|
|
22
|
+
sourceDir: string,
|
|
23
|
+
targetDir: string,
|
|
24
|
+
analysis: AdapterAnalysis,
|
|
25
|
+
): void {
|
|
26
|
+
const gitDir = path.join(targetDir, ".git");
|
|
27
|
+
|
|
28
|
+
if (analysis.gitHistory) {
|
|
29
|
+
try {
|
|
30
|
+
const patchDir = path.join(targetDir, ".forge-migration-patches");
|
|
31
|
+
fs.mkdirSync(patchDir, { recursive: true });
|
|
32
|
+
execFileSync("git", ["-C", sourceDir, "format-patch", "--all", "-o", patchDir], {
|
|
33
|
+
stdio: "pipe",
|
|
34
|
+
});
|
|
35
|
+
execFileSync("git", ["init"], { cwd: targetDir, stdio: "pipe" });
|
|
36
|
+
execFileSync("git", ["config", "user.email", "forge@warpgogol.dev"], {
|
|
37
|
+
cwd: targetDir,
|
|
38
|
+
stdio: "pipe",
|
|
39
|
+
});
|
|
40
|
+
execFileSync("git", ["config", "user.name", "Forge Migration"], {
|
|
41
|
+
cwd: targetDir,
|
|
42
|
+
stdio: "pipe",
|
|
43
|
+
});
|
|
44
|
+
const patches = fs
|
|
45
|
+
.readdirSync(patchDir)
|
|
46
|
+
.filter((f) => f.endsWith(".patch"))
|
|
47
|
+
.sort();
|
|
48
|
+
if (patches.length > 0) {
|
|
49
|
+
for (const patch of patches) {
|
|
50
|
+
execFileSync("git", ["am", path.join(patchDir, patch)], {
|
|
51
|
+
cwd: targetDir,
|
|
52
|
+
stdio: "pipe",
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
trashSync(patchDir);
|
|
57
|
+
return;
|
|
58
|
+
} catch (err) {
|
|
59
|
+
console.warn(
|
|
60
|
+
`forge: git history transfer failed, falling back to clean git init: ${err instanceof Error ? err.message : String(err)}`,
|
|
61
|
+
);
|
|
62
|
+
if (!fs.existsSync(gitDir)) {
|
|
63
|
+
execFileSync("git", ["init"], { cwd: targetDir, stdio: "pipe" });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
} else {
|
|
67
|
+
if (!fs.existsSync(gitDir)) {
|
|
68
|
+
execFileSync("git", ["init"], { cwd: targetDir, stdio: "pipe" });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>Discover and categorize git-ignored/untracked files in a source project for interactive transfer selection during onboarding.</purpose>
|
|
4
|
+
<non-goals>
|
|
5
|
+
<item>Do not import from @warpgogol/* — this module is portable.</item>
|
|
6
|
+
<item>Do not copy files — discovery and categorization only.</item>
|
|
7
|
+
</non-goals>
|
|
8
|
+
</MODULE_CONTRACT>
|
|
9
|
+
<CHANGE_SUMMARY>
|
|
10
|
+
<item>Initial: discoverIgnoredFiles function that scans source for git-ignored files, categorizes them by type (config, data, runtime-state, cache, other), and returns sized categories for operator selection.</item>
|
|
11
|
+
</CHANGE_SUMMARY>
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import fs from "node:fs";
|
|
15
|
+
import path from "node:path";
|
|
16
|
+
import { execFileSync } from "node:child_process";
|
|
17
|
+
|
|
18
|
+
export type IgnoredFileCategoryId = "config" | "data" | "runtime-state" | "cache" | "other";
|
|
19
|
+
|
|
20
|
+
export interface IgnoredFileCategory {
|
|
21
|
+
id: IgnoredFileCategoryId;
|
|
22
|
+
label: string;
|
|
23
|
+
description: string;
|
|
24
|
+
paths: string[];
|
|
25
|
+
totalSizeBytes: number;
|
|
26
|
+
fileCount: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface CategoryDef {
|
|
30
|
+
id: IgnoredFileCategoryId;
|
|
31
|
+
label: string;
|
|
32
|
+
description: string;
|
|
33
|
+
matchers: ((relPath: string) => boolean)[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const CONFIG_MATCHERS: ((p: string) => boolean)[] = [
|
|
37
|
+
(p) => p === ".env" || p.startsWith(".env."),
|
|
38
|
+
(p) => p.endsWith(".key") || p.endsWith(".pem") || p.endsWith(".crt"),
|
|
39
|
+
(p) => p === ".secret" || p.startsWith("secret"),
|
|
40
|
+
(p) => p.startsWith("secrets/"),
|
|
41
|
+
(p) => p.includes("/.env") || p.includes("/.env."),
|
|
42
|
+
(p) => p === ".envrc" || p.startsWith(".envrc."),
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
const DATA_MATCHERS: ((p: string) => boolean)[] = [
|
|
46
|
+
(p) => p.startsWith(".input/") || p.includes("/.input/"),
|
|
47
|
+
(p) => p.startsWith("input/") || p.includes("/input/"),
|
|
48
|
+
(p) => p.startsWith("inputs/") || p.includes("/inputs/"),
|
|
49
|
+
(p) => p.startsWith("data/") || p.includes("/data/"),
|
|
50
|
+
(p) => p.startsWith("assets/") || p.includes("/assets/"),
|
|
51
|
+
(p) => p.startsWith("batches/") || p.includes("/batches/"),
|
|
52
|
+
(p) => p.endsWith(".db") || p.endsWith(".sqlite") || p.endsWith(".sqlite3"),
|
|
53
|
+
(p) => p.endsWith(".ndjson") && !p.startsWith("bordbuch/"),
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
const RUNTIME_STATE_MATCHERS: ((p: string) => boolean)[] = [
|
|
57
|
+
(p) => p.startsWith("storage/") || p.includes("/storage/"),
|
|
58
|
+
(p) => p.startsWith(".state/") || p.includes("/.state/"),
|
|
59
|
+
(p) => p.startsWith("state/") || p.includes("/state/"),
|
|
60
|
+
(p) => p.endsWith(".log"),
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
const CACHE_MATCHERS: ((p: string) => boolean)[] = [
|
|
64
|
+
(p) => p.startsWith(".cache/") || p.includes("/.cache/"),
|
|
65
|
+
(p) => p.startsWith("cache/") || p.includes("/cache/"),
|
|
66
|
+
(p) => p.includes(".playwright") || p.includes("browser-profile"),
|
|
67
|
+
(p) => p.endsWith(".tmp") || p.startsWith(".tmp"),
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
const CATEGORY_DEFS: CategoryDef[] = [
|
|
71
|
+
{
|
|
72
|
+
id: "config",
|
|
73
|
+
label: "Configuration & secrets",
|
|
74
|
+
description:
|
|
75
|
+
"Environment files, API keys, certificates, and local configuration needed for the project to run.",
|
|
76
|
+
matchers: CONFIG_MATCHERS,
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
id: "data",
|
|
80
|
+
label: "Data & inputs",
|
|
81
|
+
description: "Input data, datasets, databases, and batch files used by the project.",
|
|
82
|
+
matchers: DATA_MATCHERS,
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
id: "runtime-state",
|
|
86
|
+
label: "Runtime state",
|
|
87
|
+
description: "Crawlee state, logs, and other runtime artifacts from previous executions.",
|
|
88
|
+
matchers: RUNTIME_STATE_MATCHERS,
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
id: "cache",
|
|
92
|
+
label: "Caches",
|
|
93
|
+
description: "Browser profiles, temporary files, and build caches that can be regenerated.",
|
|
94
|
+
matchers: CACHE_MATCHERS,
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
id: "other",
|
|
98
|
+
label: "Other ignored files",
|
|
99
|
+
description: "Files matched by .gitignore that don't fit into any standard category.",
|
|
100
|
+
matchers: [],
|
|
101
|
+
},
|
|
102
|
+
];
|
|
103
|
+
|
|
104
|
+
const CATEGORY_PRIORITY: Record<IgnoredFileCategoryId, number> = {
|
|
105
|
+
config: 0,
|
|
106
|
+
data: 1,
|
|
107
|
+
"runtime-state": 2,
|
|
108
|
+
cache: 3,
|
|
109
|
+
other: 4,
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
function categorizePath(relPath: string): CategoryDef {
|
|
113
|
+
const normalized = relPath.replace(/\\/g, "/");
|
|
114
|
+
for (const def of CATEGORY_DEFS) {
|
|
115
|
+
if (def.matchers.some((m) => m(normalized))) {
|
|
116
|
+
return def;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return CATEGORY_DEFS[CATEGORY_DEFS.length - 1];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function getDirStats(dirPath: string): { size: number; count: number } {
|
|
123
|
+
let size = 0;
|
|
124
|
+
let count = 0;
|
|
125
|
+
try {
|
|
126
|
+
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
127
|
+
for (const entry of entries) {
|
|
128
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
129
|
+
if (entry.isDirectory()) {
|
|
130
|
+
const sub = getDirStats(fullPath);
|
|
131
|
+
size += sub.size;
|
|
132
|
+
count += sub.count;
|
|
133
|
+
} else if (entry.isFile()) {
|
|
134
|
+
try {
|
|
135
|
+
size += fs.statSync(fullPath).size;
|
|
136
|
+
count++;
|
|
137
|
+
} catch {
|
|
138
|
+
// File may have been removed
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
} catch {
|
|
143
|
+
// Directory may have been removed
|
|
144
|
+
}
|
|
145
|
+
return { size, count };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function getPathSize(fullPath: string): { sizeBytes: number; fileCount: number } {
|
|
149
|
+
try {
|
|
150
|
+
const stat = fs.statSync(fullPath);
|
|
151
|
+
if (stat.isDirectory()) {
|
|
152
|
+
const { size, count } = getDirStats(fullPath);
|
|
153
|
+
return { sizeBytes: size, fileCount: count };
|
|
154
|
+
}
|
|
155
|
+
return { sizeBytes: stat.size, fileCount: 1 };
|
|
156
|
+
} catch {
|
|
157
|
+
return { sizeBytes: 0, fileCount: 0 };
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function discoverIgnoredFiles(sourceDir: string): IgnoredFileCategory[] {
|
|
162
|
+
const hasGit = fs.existsSync(path.join(sourceDir, ".git"));
|
|
163
|
+
let ignoredPaths: string[] = [];
|
|
164
|
+
|
|
165
|
+
if (hasGit) {
|
|
166
|
+
try {
|
|
167
|
+
const output = execFileSync("git", ["-C", sourceDir, "status", "--ignored", "--porcelain"], {
|
|
168
|
+
encoding: "utf-8",
|
|
169
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
170
|
+
timeout: 30_000,
|
|
171
|
+
});
|
|
172
|
+
ignoredPaths = output
|
|
173
|
+
.split("\n")
|
|
174
|
+
.filter((l) => l.startsWith("!! "))
|
|
175
|
+
.map((l) => l.slice(3).trim())
|
|
176
|
+
.filter((p) => p.length > 0);
|
|
177
|
+
} catch {
|
|
178
|
+
// git command failed — return empty
|
|
179
|
+
return [];
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (ignoredPaths.length === 0) {
|
|
184
|
+
return [];
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const categoryMap = new Map<IgnoredFileCategoryId, IgnoredFileCategory>();
|
|
188
|
+
|
|
189
|
+
for (const relPath of ignoredPaths) {
|
|
190
|
+
const fullPath = path.join(sourceDir, relPath);
|
|
191
|
+
const def = categorizePath(relPath);
|
|
192
|
+
const { sizeBytes, fileCount } = getPathSize(fullPath);
|
|
193
|
+
|
|
194
|
+
if (!categoryMap.has(def.id)) {
|
|
195
|
+
categoryMap.set(def.id, {
|
|
196
|
+
id: def.id,
|
|
197
|
+
label: def.label,
|
|
198
|
+
description: def.description,
|
|
199
|
+
paths: [],
|
|
200
|
+
totalSizeBytes: 0,
|
|
201
|
+
fileCount: 0,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const cat = categoryMap.get(def.id)!;
|
|
206
|
+
cat.paths.push(relPath);
|
|
207
|
+
cat.totalSizeBytes += sizeBytes;
|
|
208
|
+
cat.fileCount += fileCount;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return Array.from(categoryMap.values()).sort(
|
|
212
|
+
(a, b) => CATEGORY_PRIORITY[a.id] - CATEGORY_PRIORITY[b.id],
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export function formatSize(bytes: number): string {
|
|
217
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
218
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
219
|
+
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
220
|
+
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
|
|
221
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>Barrel export for migration-adapter module (RFC-0546).</purpose>
|
|
4
|
+
<non-goals>
|
|
5
|
+
<item>Do not re-export implementation details — only public types and registry functions.</item>
|
|
6
|
+
</non-goals>
|
|
7
|
+
</MODULE_CONTRACT>
|
|
8
|
+
<CHANGE_SUMMARY>
|
|
9
|
+
<item>RFC-0546: initial barrel export for migration-adapters.</item>
|
|
10
|
+
</CHANGE_SUMMARY>
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export type { MigrationAdapter, AdapterAnalysis, MigrationResult, Conflict } from "./types.ts";
|
|
14
|
+
|
|
15
|
+
export { FORGE_PROTECTED_PATHS, DEFAULT_EXCLUDE_PATTERNS } from "./types.ts";
|
|
16
|
+
|
|
17
|
+
export { nodeTypescriptPnpmAdapter } from "./node-typescript-pnpm/index.ts";
|
|
18
|
+
export { phaserPnpmAdapter } from "./phaser-pnpm/index.ts";
|
|
19
|
+
export { getAdapters, detectAdapter, detectAdapters } from "./registry.ts";
|
|
20
|
+
export { discoverIgnoredFiles, formatSize } from "./ignored-files.ts";
|
|
21
|
+
export type { IgnoredFileCategory, IgnoredFileCategoryId } from "./ignored-files.ts";
|