@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,113 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>Unit tests for domain-aware forge.doctor (RFC-0640).</purpose>
|
|
4
|
+
</MODULE_CONTRACT>
|
|
5
|
+
<CHANGE_SUMMARY>
|
|
6
|
+
<item>RFC-0640: initial domain-aware doctor tests.</item>
|
|
7
|
+
</CHANGE_SUMMARY>
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { test, expect, beforeEach, afterEach } from "vitest";
|
|
11
|
+
import { mkdtemp, rm, mkdir, writeFile } from "node:fs/promises";
|
|
12
|
+
import { tmpdir } from "node:os";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
import { runDoctor } from "../onboarding/doctor.ts";
|
|
15
|
+
import type { ForgeCommandInput, ForgeRuntimeContext } from "../types.ts";
|
|
16
|
+
|
|
17
|
+
let tempDir: string;
|
|
18
|
+
|
|
19
|
+
beforeEach(async () => {
|
|
20
|
+
tempDir = await mkdtemp(join(tmpdir(), "doctor-domain-test-"));
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
afterEach(async () => {
|
|
24
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
function makeContext(): ForgeRuntimeContext {
|
|
28
|
+
return {
|
|
29
|
+
workspaceRoot: tempDir,
|
|
30
|
+
logger: {
|
|
31
|
+
section: () => {},
|
|
32
|
+
success: () => {},
|
|
33
|
+
warn: () => {},
|
|
34
|
+
error: () => {},
|
|
35
|
+
info: () => {},
|
|
36
|
+
},
|
|
37
|
+
dryRun: false,
|
|
38
|
+
outputFormat: "json",
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
test("doctor reports default software domain when no forge.yaml", async () => {
|
|
43
|
+
const input: ForgeCommandInput = { argv: [], flags: {} };
|
|
44
|
+
const result = await runDoctor(input, makeContext());
|
|
45
|
+
|
|
46
|
+
expect(result.data!.domain).toBeDefined();
|
|
47
|
+
expect(result.data!.domain!.domain).toBe("software");
|
|
48
|
+
expect(result.data!.domain!.source).toBe("default");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("doctor reports domain from forge.yaml when present", async () => {
|
|
52
|
+
await writeFile(
|
|
53
|
+
join(tempDir, "forge.yaml"),
|
|
54
|
+
`schema: "forge/config@1"\nproject:\n name: test-project\n stack: []\n packageManager: pnpm\n domain: video\npaths:\n rfcsDir: docs/rfcs\n adrsDir: docs/adrs\n plansDir: docs/plans\n auditsDir: docs/audits\n specsDir: docs/specs\n skillsDir: .agents/skills\n sessionsDir: docs/sessions\n`,
|
|
55
|
+
"utf8",
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const input: ForgeCommandInput = { argv: [], flags: {} };
|
|
59
|
+
const result = await runDoctor(input, makeContext());
|
|
60
|
+
|
|
61
|
+
expect(result.data!.domain).toBeDefined();
|
|
62
|
+
expect(result.data!.domain!.domain).toBe("video");
|
|
63
|
+
expect(result.data!.domain!.source).toBe("forge.yaml");
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("doctor includes domain-info check in checks array", async () => {
|
|
67
|
+
const input: ForgeCommandInput = { argv: [], flags: {} };
|
|
68
|
+
const result = await runDoctor(input, makeContext());
|
|
69
|
+
|
|
70
|
+
const domainCheck = result.data!.checks.find((c) => c.name === "domain-info");
|
|
71
|
+
expect(domainCheck).toBeDefined();
|
|
72
|
+
expect(domainCheck!.status).toBe("pass");
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("doctor --strict does not affect invariant reporting (reserved for future use)", async () => {
|
|
76
|
+
await mkdir(join(tempDir, "packages", "forge", "profiles"), { recursive: true });
|
|
77
|
+
await writeFile(
|
|
78
|
+
join(tempDir, "packages", "forge", "profiles", "test.yaml"),
|
|
79
|
+
`schema: "forge/stack-profile@1"\nid: test\ndisplayName: Test\ndetect:\n anyOf: ["package.json"]\nworkspace:\n dirs: ["packages"]\n files: []\ninstall: []\ndomain: video\ninvariants:\n - id: VIDEO-01\n rule: "All videos must have audio"\n severity: error\n`,
|
|
80
|
+
"utf8",
|
|
81
|
+
);
|
|
82
|
+
await writeFile(
|
|
83
|
+
join(tempDir, "forge.yaml"),
|
|
84
|
+
`schema: "forge/config@1"\nproject:\n name: test-project\n stack: ["test"]\n packageManager: pnpm\npaths:\n rfcsDir: docs/rfcs\n adrsDir: docs/adrs\n plansDir: docs/plans\n auditsDir: docs/audits\n specsDir: docs/specs\n skillsDir: .agents/skills\n sessionsDir: docs/sessions\n`,
|
|
85
|
+
"utf8",
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
const input: ForgeCommandInput = { argv: [], flags: { strict: true } };
|
|
89
|
+
const result = await runDoctor(input, makeContext());
|
|
90
|
+
|
|
91
|
+
const invariantsCheck = result.data!.checks.find((c) => c.name === "domain-invariants");
|
|
92
|
+
expect(invariantsCheck).toBeDefined();
|
|
93
|
+
// --strict elevates warn to fail for domain-invariants and profile-validate,
|
|
94
|
+
// but invariants check is "pass" here (invariants declared), so no elevation
|
|
95
|
+
expect(invariantsCheck!.status).toBe("pass");
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test("doctor runs nested AGENTS.md check for non-software domain (RFC-0640 fix)", async () => {
|
|
99
|
+
await writeFile(
|
|
100
|
+
join(tempDir, "forge.yaml"),
|
|
101
|
+
`schema: "forge/config@1"\nproject:\n name: test-project\n stack: []\n packageManager: pnpm\n domain: video\npaths:\n rfcsDir: docs/rfcs\n adrsDir: docs/adrs\n plansDir: docs/plans\n auditsDir: docs/audits\n specsDir: docs/specs\n skillsDir: .agents/skills\n sessionsDir: docs/sessions\n`,
|
|
102
|
+
"utf8",
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
const input: ForgeCommandInput = { argv: [], flags: {} };
|
|
106
|
+
const result = await runDoctor(input, makeContext());
|
|
107
|
+
|
|
108
|
+
// RFC-0640 fix: nested AGENTS.md check now runs for all domains
|
|
109
|
+
// (previously gated by isSoftwareDomain, which skipped non-software domains)
|
|
110
|
+
const nestedCheck = result.data!.checks.find((c) => c.name === "nested-AGENTS.md");
|
|
111
|
+
expect(nestedCheck).toBeDefined();
|
|
112
|
+
expect(nestedCheck!.status).toBe("pass");
|
|
113
|
+
});
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { test, expect, describe, beforeAll, afterAll } from "vitest";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { mkdtemp, rm, mkdir, writeFile, readFile } from "node:fs/promises";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import type { ForgeCommandInput, ForgeRuntimeContext, ForgeFlagValue } from "../../src/types.ts";
|
|
6
|
+
import { runExplorationList } from "../../os/exploration/handlers/list.ts";
|
|
7
|
+
import { runExplorationShow } from "../../os/exploration/handlers/show.ts";
|
|
8
|
+
import { runExplorationArchive } from "../../os/exploration/handlers/archive.ts";
|
|
9
|
+
import type {
|
|
10
|
+
ExplorationListResult,
|
|
11
|
+
ExplorationShowResult,
|
|
12
|
+
ExplorationArchiveResult,
|
|
13
|
+
} from "../../os/exploration/types.ts";
|
|
14
|
+
|
|
15
|
+
async function makeTempDir(): Promise<string> {
|
|
16
|
+
return mkdtemp(join(tmpdir(), "forge-exploration-test-"));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function makeInput(flags: Record<string, ForgeFlagValue> = {}): ForgeCommandInput {
|
|
20
|
+
return { argv: [], flags };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function makeContext(workspaceRoot: string): ForgeRuntimeContext {
|
|
24
|
+
return {
|
|
25
|
+
workspaceRoot,
|
|
26
|
+
logger: {
|
|
27
|
+
info: () => {},
|
|
28
|
+
warn: () => {},
|
|
29
|
+
error: () => {},
|
|
30
|
+
success: () => {},
|
|
31
|
+
section: () => {},
|
|
32
|
+
},
|
|
33
|
+
dryRun: false,
|
|
34
|
+
outputFormat: "json",
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const SAMPLE_NOTE = `---
|
|
39
|
+
id: self-hosted-fonts
|
|
40
|
+
title: "Self-hosted fonts vs CDN"
|
|
41
|
+
createdAt: 2026-08-06
|
|
42
|
+
status: open
|
|
43
|
+
related: []
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
# Exploration: Self-hosted fonts vs CDN
|
|
47
|
+
|
|
48
|
+
## Idea
|
|
49
|
+
|
|
50
|
+
Should we self-host fonts or keep using the CDN?
|
|
51
|
+
|
|
52
|
+
## Options
|
|
53
|
+
|
|
54
|
+
### Option 1: Self-host
|
|
55
|
+
- **Approach:** Download fonts and serve locally.
|
|
56
|
+
- **Trade-offs:** More bandwidth, better privacy.
|
|
57
|
+
|
|
58
|
+
### Option 2: Keep CDN
|
|
59
|
+
- **Approach:** Continue using Google Fonts CDN.
|
|
60
|
+
- **Trade-offs:** Less control, faster initial load.
|
|
61
|
+
`;
|
|
62
|
+
|
|
63
|
+
const ARCHIVED_NOTE = `---
|
|
64
|
+
id: old-idea
|
|
65
|
+
title: "Old idea that was explored"
|
|
66
|
+
createdAt: 2026-07-01
|
|
67
|
+
status: archived
|
|
68
|
+
related: [RFC-0700]
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
# Exploration: Old idea
|
|
72
|
+
|
|
73
|
+
Already archived.
|
|
74
|
+
`;
|
|
75
|
+
|
|
76
|
+
describe("exploration.list", () => {
|
|
77
|
+
let dir: string;
|
|
78
|
+
|
|
79
|
+
beforeAll(async () => {
|
|
80
|
+
dir = await makeTempDir();
|
|
81
|
+
await mkdir(join(dir, "docs/explorations"), { recursive: true });
|
|
82
|
+
await writeFile(join(dir, "docs/explorations/self-hosted-fonts.md"), SAMPLE_NOTE, "utf-8");
|
|
83
|
+
await writeFile(join(dir, "docs/explorations/old-idea.md"), ARCHIVED_NOTE, "utf-8");
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
afterAll(async () => {
|
|
87
|
+
await rm(dir, { recursive: true, force: true });
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("lists all exploration notes", async () => {
|
|
91
|
+
const result = await runExplorationList(makeInput(), makeContext(dir));
|
|
92
|
+
const data = result.data as ExplorationListResult;
|
|
93
|
+
|
|
94
|
+
expect(data.command).toBe("exploration.list");
|
|
95
|
+
expect(data.status).toBe("ok");
|
|
96
|
+
expect(data.count).toBe(2);
|
|
97
|
+
expect(data.explorations).toHaveLength(2);
|
|
98
|
+
|
|
99
|
+
const ids = data.explorations.map((e) => e.id).sort();
|
|
100
|
+
expect(ids).toEqual(["old-idea", "self-hosted-fonts"]);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("filters by status", async () => {
|
|
104
|
+
const result = await runExplorationList(makeInput({ status: "archived" }), makeContext(dir));
|
|
105
|
+
const data = result.data as ExplorationListResult;
|
|
106
|
+
|
|
107
|
+
expect(data.count).toBe(1);
|
|
108
|
+
expect(data.explorations[0]!.id).toBe("old-idea");
|
|
109
|
+
expect(data.explorations[0]!.status).toBe("archived");
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test("returns empty list when directory does not exist", async () => {
|
|
113
|
+
const emptyDir = await makeTempDir();
|
|
114
|
+
try {
|
|
115
|
+
const result = await runExplorationList(makeInput(), makeContext(emptyDir));
|
|
116
|
+
const data = result.data as ExplorationListResult;
|
|
117
|
+
|
|
118
|
+
expect(data.command).toBe("exploration.list");
|
|
119
|
+
expect(data.status).toBe("ok");
|
|
120
|
+
expect(data.count).toBe(0);
|
|
121
|
+
expect(data.explorations).toEqual([]);
|
|
122
|
+
expect(result.exitCode).toBeUndefined();
|
|
123
|
+
} finally {
|
|
124
|
+
await rm(emptyDir, { recursive: true, force: true });
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
describe("exploration.show", () => {
|
|
130
|
+
let dir: string;
|
|
131
|
+
|
|
132
|
+
beforeAll(async () => {
|
|
133
|
+
dir = await makeTempDir();
|
|
134
|
+
await mkdir(join(dir, "docs/explorations"), { recursive: true });
|
|
135
|
+
await writeFile(join(dir, "docs/explorations/self-hosted-fonts.md"), SAMPLE_NOTE, "utf-8");
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
afterAll(async () => {
|
|
139
|
+
await rm(dir, { recursive: true, force: true });
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("shows the full content of an exploration note", async () => {
|
|
143
|
+
const result = await runExplorationShow(
|
|
144
|
+
makeInput({ id: "self-hosted-fonts" }),
|
|
145
|
+
makeContext(dir),
|
|
146
|
+
);
|
|
147
|
+
const data = result.data as ExplorationShowResult;
|
|
148
|
+
|
|
149
|
+
expect(data.command).toBe("exploration.show");
|
|
150
|
+
expect(data.status).toBe("ok");
|
|
151
|
+
expect(data.note.id).toBe("self-hosted-fonts");
|
|
152
|
+
expect(data.note.title).toBe("Self-hosted fonts vs CDN");
|
|
153
|
+
expect(data.note.status).toBe("open");
|
|
154
|
+
expect(data.note.createdAt).toBe("2026-08-06");
|
|
155
|
+
expect(data.note.related).toEqual([]);
|
|
156
|
+
expect(data.note.body).toContain("# Exploration: Self-hosted fonts vs CDN");
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test("returns exit code 1 when slug not found", async () => {
|
|
160
|
+
const result = await runExplorationShow(makeInput({ id: "nonexistent" }), makeContext(dir));
|
|
161
|
+
const data = result.data as ExplorationShowResult;
|
|
162
|
+
|
|
163
|
+
expect(data.status).toBe("error");
|
|
164
|
+
expect(result.exitCode).toBe(1);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test("returns exit code 1 when --id is missing", async () => {
|
|
168
|
+
const result = await runExplorationShow(makeInput(), makeContext(dir));
|
|
169
|
+
const data = result.data as ExplorationShowResult;
|
|
170
|
+
|
|
171
|
+
expect(data.status).toBe("error");
|
|
172
|
+
expect(result.exitCode).toBe(1);
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
describe("exploration.archive", () => {
|
|
177
|
+
let dir: string;
|
|
178
|
+
|
|
179
|
+
beforeAll(async () => {
|
|
180
|
+
dir = await makeTempDir();
|
|
181
|
+
await mkdir(join(dir, "docs/explorations"), { recursive: true });
|
|
182
|
+
await writeFile(join(dir, "docs/explorations/self-hosted-fonts.md"), SAMPLE_NOTE, "utf-8");
|
|
183
|
+
await writeFile(join(dir, "docs/explorations/old-idea.md"), ARCHIVED_NOTE, "utf-8");
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
afterAll(async () => {
|
|
187
|
+
await rm(dir, { recursive: true, force: true });
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test("transitions status from open to archived", async () => {
|
|
191
|
+
const result = await runExplorationArchive(
|
|
192
|
+
makeInput({ id: "self-hosted-fonts" }),
|
|
193
|
+
makeContext(dir),
|
|
194
|
+
);
|
|
195
|
+
const data = result.data as ExplorationArchiveResult;
|
|
196
|
+
|
|
197
|
+
expect(data.command).toBe("exploration.archive");
|
|
198
|
+
expect(data.status).toBe("ok");
|
|
199
|
+
expect(data.id).toBe("self-hosted-fonts");
|
|
200
|
+
expect(data.previousStatus).toBe("open");
|
|
201
|
+
expect(data.newStatus).toBe("archived");
|
|
202
|
+
|
|
203
|
+
const content = await readFile(join(dir, "docs/explorations/self-hosted-fonts.md"), "utf-8");
|
|
204
|
+
expect(content).toContain("status: archived");
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
test("adds RFC id to related field", async () => {
|
|
208
|
+
const result = await runExplorationArchive(
|
|
209
|
+
makeInput({ id: "self-hosted-fonts", rfc: "RFC-0710" }),
|
|
210
|
+
makeContext(dir),
|
|
211
|
+
);
|
|
212
|
+
const data = result.data as ExplorationArchiveResult;
|
|
213
|
+
|
|
214
|
+
expect(data.related).toContain("RFC-0710");
|
|
215
|
+
|
|
216
|
+
const content = await readFile(join(dir, "docs/explorations/self-hosted-fonts.md"), "utf-8");
|
|
217
|
+
expect(content).toContain("RFC-0710");
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
test("is idempotent when already archived", async () => {
|
|
221
|
+
const result = await runExplorationArchive(makeInput({ id: "old-idea" }), makeContext(dir));
|
|
222
|
+
const data = result.data as ExplorationArchiveResult;
|
|
223
|
+
|
|
224
|
+
expect(data.status).toBe("ok");
|
|
225
|
+
expect(data.previousStatus).toBe("archived");
|
|
226
|
+
expect(data.newStatus).toBe("archived");
|
|
227
|
+
expect(result.exitCode).toBe(0);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
test("returns exit code 1 when slug not found", async () => {
|
|
231
|
+
const result = await runExplorationArchive(makeInput({ id: "nonexistent" }), makeContext(dir));
|
|
232
|
+
const data = result.data as ExplorationArchiveResult;
|
|
233
|
+
|
|
234
|
+
expect(data.status).toBe("error");
|
|
235
|
+
expect(result.exitCode).toBe(1);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
test("returns exit code 1 when --id is missing", async () => {
|
|
239
|
+
const result = await runExplorationArchive(makeInput(), makeContext(dir));
|
|
240
|
+
const data = result.data as ExplorationArchiveResult;
|
|
241
|
+
|
|
242
|
+
expect(data.status).toBe("error");
|
|
243
|
+
expect(result.exitCode).toBe(1);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
test("rejects invalid slug (non-kebab-case)", async () => {
|
|
247
|
+
const result = await runExplorationArchive(
|
|
248
|
+
makeInput({ id: "Invalid_Slug!" }),
|
|
249
|
+
makeContext(dir),
|
|
250
|
+
);
|
|
251
|
+
const data = result.data as ExplorationArchiveResult;
|
|
252
|
+
|
|
253
|
+
expect(data.status).toBe("error");
|
|
254
|
+
expect(result.exitCode).toBe(1);
|
|
255
|
+
});
|
|
256
|
+
});
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
GENERATED. Do not change this line unless the file contains project specific changes.
|
|
3
|
+
DO NOT EDIT THIS FILE. Changes are overwritten on the next build.
|
|
4
|
+
Owner command: forge.agents.generate
|
|
5
|
+
Edit instead: the forge.agents.generate generator source (not this file).
|
|
6
|
+
Regenerate: forge forge.agents.generate
|
|
7
|
+
-->
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
<!-- Root AGENTS.md template for business register projects.
|
|
11
|
+
Static prose only — dynamic sections (skills, capabilities, behavioral layer)
|
|
12
|
+
are inserted at the dynamicSections marker by runAgentsGenerate.
|
|
13
|
+
Placeholders: projectName, projectStack, projectPm,
|
|
14
|
+
rfcsDir, adrsDir, plansDir, auditsDir, specsDir,
|
|
15
|
+
skillsDir, dynamicSections -->
|
|
16
|
+
# Agent Guide: test-project
|
|
17
|
+
|
|
18
|
+
> This file is generated by `forge.agents.generate` from `forge.yaml`.
|
|
19
|
+
> Do not edit by hand — edit `forge.yaml` and regenerate.
|
|
20
|
+
|
|
21
|
+
## Project
|
|
22
|
+
|
|
23
|
+
- **Name:** test-project
|
|
24
|
+
- **Stack:** typescript
|
|
25
|
+
- **Package manager:** pnpm
|
|
26
|
+
|
|
27
|
+
## Paths
|
|
28
|
+
|
|
29
|
+
- RFCs: `docs/rfcs`
|
|
30
|
+
- ADRs: `docs/adrs`
|
|
31
|
+
- Plans: `docs/plans`
|
|
32
|
+
- Audits: `docs/audits`
|
|
33
|
+
- Specs: `docs/specs`
|
|
34
|
+
- Skills: `.agents/skills`
|
|
35
|
+
|
|
36
|
+
## Skills
|
|
37
|
+
|
|
38
|
+
| Name | Category | Invocation | Concerns |
|
|
39
|
+
| --- | --- | --- | --- |
|
|
40
|
+
| fo-add-tests | fo | user | code-mutation |
|
|
41
|
+
| fo-architecture | fo | user | document-only |
|
|
42
|
+
| fo-compass-annotate | fo | user | content-mutation |
|
|
43
|
+
| fo-design-summit | fo | user | document-only |
|
|
44
|
+
| fo-doc-audit | fo | user | document-only |
|
|
45
|
+
| fo-explore | fo | user | document-only |
|
|
46
|
+
| fo-extract-dna | fo | user | document-only |
|
|
47
|
+
| fo-fix | fo | user | code-mutation |
|
|
48
|
+
| fo-handoff | fo | user | document-only |
|
|
49
|
+
| fo-harvest | fo | user | code-mutation |
|
|
50
|
+
| fo-idea | fo | user | document-only |
|
|
51
|
+
| fo-idea-audit | fo | user | read-only |
|
|
52
|
+
| fo-idea-create-adr | fo | user | document-only |
|
|
53
|
+
| fo-idea-create-rfc | fo | user | document-only |
|
|
54
|
+
| fo-idea-enhance | fo | user | document-only |
|
|
55
|
+
| fo-idea-i-just-want-to-see-the-plan | fo | user | document-only |
|
|
56
|
+
| fo-idea-i-just-want-to-see-the-result | fo | user | code-mutation |
|
|
57
|
+
| fo-idea-implement | fo | user | code-mutation |
|
|
58
|
+
| fo-idea-plan | fo | user | document-only |
|
|
59
|
+
| fo-idea-status | fo | user | read-only |
|
|
60
|
+
| fo-knowledge-distill | fo | user | document-only |
|
|
61
|
+
| fo-memory-sync | fo | user | content-mutation |
|
|
62
|
+
| fo-qa | fo | user | document-only |
|
|
63
|
+
| fo-review | fo | user | read-only |
|
|
64
|
+
| fo-session-retro | fo | user | document-only |
|
|
65
|
+
| fo-session-save | fo | user | document-only |
|
|
66
|
+
| fo-spec-ingest | fo | user | document-only |
|
|
67
|
+
| fo-step-commit | fo | model | code-mutation |
|
|
68
|
+
| fo-triage | fo | user | document-only |
|
|
69
|
+
| forge-bootstrap | meta | user | content-mutation |
|
|
70
|
+
| grilling | shared | user | read-only |
|
|
71
|
+
| my-preferences | shared | user | document-only |
|
|
72
|
+
| port-to-forge | meta | user | code-mutation |
|
|
73
|
+
| skill-create | meta | user | document-only |
|
|
74
|
+
| windows-ai-tooling | shared | user | code-mutation |
|
|
75
|
+
| writing-great-skills | shared | user | read-only |
|
|
76
|
+
|
|
77
|
+
## Capabilities
|
|
78
|
+
|
|
79
|
+
Bindings resolved from `forge.yaml`:
|
|
80
|
+
|
|
81
|
+
| Key | Status | Value |
|
|
82
|
+
| --- | --- | --- |
|
|
83
|
+
| commands.validateRfc | resolved | `pnpm exec forge rfc.validate` |
|
|
84
|
+
| commands.validateAdr | resolved | `pnpm exec forge adr.validate` |
|
|
85
|
+
| commands.typecheck | absent | — |
|
|
86
|
+
| commands.test | absent | — |
|
|
87
|
+
| commands.scopedBuild | absent | — |
|
|
88
|
+
| commands.specValidate | resolved | `pnpm exec forge spec.validate` |
|
|
89
|
+
| commands.sessionSave | resolved | `pnpm exec forge session.save` |
|
|
90
|
+
| paths.invariantsFile | resolved | `docs/architecture-dna.md` |
|
|
91
|
+
| paths.reviewsDir | resolved | `docs/reviews` |
|
|
92
|
+
| paths.handoffsDir | resolved | `docs/handoffs` |
|
|
93
|
+
| paths.sessionsDir | resolved | `docs/sessions` |
|
|
94
|
+
| paths.compassDocs | resolved | `docs/requirements.xml` |
|
|
95
|
+
|
|
96
|
+
<!-- forge:begin behavioral-layer -->
|
|
97
|
+
|
|
98
|
+
## Behavioral layer
|
|
99
|
+
|
|
100
|
+
This section defines the agent's core behavioral contract. It is generated from skill `triggers` and fixed policy text. The agent MUST follow these behaviors in every session.
|
|
101
|
+
|
|
102
|
+
### Intent-to-skill routing
|
|
103
|
+
|
|
104
|
+
When the operator expresses an intent in natural language, the agent routes to the matching skill. The routing table is generated from `triggers` fields in skill frontmatter.
|
|
105
|
+
|
|
106
|
+
| Operator says something like | Skill |
|
|
107
|
+
| --- | --- |
|
|
108
|
+
|
|
109
|
+
The agent uses judgment to calibrate routing — minor edits (typo fixes, small CSS changes) do not require skill invocation, while significant changes (new features, architectural decisions) do.
|
|
110
|
+
|
|
111
|
+
### Auto-grilling
|
|
112
|
+
|
|
113
|
+
When the operator describes a significant idea or change, the agent SHOULD proactively invoke grilling to stress-test the plan before building.
|
|
114
|
+
|
|
115
|
+
- **Significant:** new feature, architectural change, new RFC/ADR, cross-workspace refactor.
|
|
116
|
+
- **Minor:** typo fix, small CSS change, renaming a variable, updating a dependency version.
|
|
117
|
+
- The operator can say "just do it" to skip grilling — the agent invokes `fo-idea-i-just-want-to-see-the-result` instead.
|
|
118
|
+
|
|
119
|
+
### Auto-session-save
|
|
120
|
+
|
|
121
|
+
The agent SHOULD auto-save sessions at the end of each session unless opted out via `PREFERENCES.md` (`saveSessions: false`). Companion-mode session saving can be opted out separately.
|
|
122
|
+
|
|
123
|
+
Session save mechanism:
|
|
124
|
+
1. Export the conversation as a raw ATIF file to `docs/sessions/.raw/` (create the directory with `mkdir -p docs/sessions/.raw` if it does not exist).
|
|
125
|
+
2. Run `ref(forge.yaml bindings.commands.sessionSave)` to convert the raw file to structured markdown in `docs/sessions/`.
|
|
126
|
+
3. If the command fails or the binding is null, create the session file manually in `docs/sessions/` with frontmatter (id, date, type, relatedRfcs).
|
|
127
|
+
|
|
128
|
+
### Auto-review
|
|
129
|
+
|
|
130
|
+
The agent SHOULD auto-run `fo-review` after implementing a significant change. Review results are presented in creator language — only actionable issues are highlighted.
|
|
131
|
+
|
|
132
|
+
### Context awareness
|
|
133
|
+
|
|
134
|
+
Before starting significant work, the agent SHOULD read recent ADRs, RFCs, and session transcripts (last 5-10 documents) to understand prior decisions and avoid conflicts. Minor edits do not require context reading.
|
|
135
|
+
|
|
136
|
+
### Creator-facing communication
|
|
137
|
+
|
|
138
|
+
The agent communicates in creator language — no CLI commands, no skill names, no internal jargon in user-facing text.
|
|
139
|
+
|
|
140
|
+
- **Forbidden terms in user-facing text:** `pnpm`, `git commit`, `vitest`, `tsc`, `AGENTS.md`, `forge.yaml`, `RFC-XXXX`, `fo-idea`, `fo-fix`, `fo-review`.
|
|
141
|
+
- **Use instead:** "I'll review the plan with you", "I'll check the code quality", "I'll prepare the changes for you".
|
|
142
|
+
- CLI output and internal logs remain technical — only agent chat output uses creator language.
|
|
143
|
+
|
|
144
|
+
### Adaptive learning
|
|
145
|
+
|
|
146
|
+
The agent reads `.agents/operator-profile.md` at the start of each session and calibrates behavior based on the operator's known preferences, communication style, and past feedback.
|
|
147
|
+
|
|
148
|
+
- The profile is local to the project, git-tracked, and can be deleted by the operator at any time.
|
|
149
|
+
- Developer handoff summaries MUST NOT include `operator-profile.md` contents — only technical architecture, decisions, and code structure.
|
|
150
|
+
- Sections are tagged with Zugangsstufen (Öffentlich/Vertraulich). Only Öffentlich sections are visible to co-creators.
|
|
151
|
+
- Entries in `## Emotional rhythm` and `## Feedback history` expire after 90 days unless refreshed. Stale entries are marked `[expired YYYY-MM-DD]`.
|
|
152
|
+
- The profile is gitignored by default to protect operator privacy.
|
|
153
|
+
|
|
154
|
+
### Proactive guidance
|
|
155
|
+
|
|
156
|
+
The agent offers proactive guidance at the right moment — at most once per session per topic. If declined, the suggestion is not repeated.
|
|
157
|
+
|
|
158
|
+
Built-in guidance triggers:
|
|
159
|
+
- **Long session** (>2 hours): suggest a break or session save.
|
|
160
|
+
- **Complex change** (3+ files in one area): suggest grilling or planning.
|
|
161
|
+
- **Multiple topics** in one session: suggest splitting into separate sessions.
|
|
162
|
+
- **Large scope** (>500 lines changed): suggest an RFC or ADR.
|
|
163
|
+
- **Unclear request**: ask clarifying questions before proceeding.
|
|
164
|
+
|
|
165
|
+
### Live operator feedback
|
|
166
|
+
|
|
167
|
+
The agent updates `.agents/operator-profile.md` immediately when the operator expresses a behavior preference (e.g., "I prefer shorter responses", "don't ask me about tests"). The agent confirms understanding before updating: "Just to make sure I understand — you want me to [X] from now on?"
|
|
168
|
+
|
|
169
|
+
### Register parameter
|
|
170
|
+
|
|
171
|
+
The current register is **business**.
|
|
172
|
+
|
|
173
|
+
- **Business register:** core behavioral layer only — professional, efficient communication.
|
|
174
|
+
- **Creative register:** core + extended behavioral layer — creative partnership, emotional support, companion mode.
|
|
175
|
+
- The register can be changed at any time via live operator feedback. The change takes effect immediately.
|
|
176
|
+
|
|
177
|
+
### Pushback policy
|
|
178
|
+
|
|
179
|
+
The agent exercises two classes of pushback:
|
|
180
|
+
|
|
181
|
+
1. **Purpose-drift (soft):** when the operator's request drifts from the project's stated purpose, the agent offers a gentle reminder. The operator can override without explicit confirmation.
|
|
182
|
+
2. **Legal/compliance (hard):** when the operator's request may violate copyright, GDPR/DSGVO, accessibility, or license requirements, the agent refuses and explains the risk. The operator can override only with explicit confirmation that they accept the risk.
|
|
183
|
+
|
|
184
|
+
### External capabilities (MCP)
|
|
185
|
+
|
|
186
|
+
The agent uses a two-tier model for external capabilities:
|
|
187
|
+
|
|
188
|
+
1. **Read-only autonomous:** the agent may use read-only MCP tools (web search, documentation lookup) without asking.
|
|
189
|
+
2. **Connectable (offered):** the agent offers connectable capabilities (email, calendar, analytics) and lets the operator choose. The agent MUST NOT auto-select a specific provider.
|
|
190
|
+
|
|
191
|
+
### Safety net and graceful failure
|
|
192
|
+
|
|
193
|
+
The agent provides a safety net for the operator:
|
|
194
|
+
|
|
195
|
+
- **Undo/rollback:** the agent offers undo or rollback for significant changes.
|
|
196
|
+
- **Auto-recovery:** when something goes wrong, the agent attempts automatic recovery before reporting.
|
|
197
|
+
- **No technical errors shown:** the agent MUST NOT show technical errors, stack traces, or error codes to the operator. Errors are translated to creator language: "Something went wrong with the preview. Let me try again."
|
|
198
|
+
|
|
199
|
+
### Invisible quality
|
|
200
|
+
|
|
201
|
+
The agent handles performance, accessibility, SEO, and optimization automatically. Quality is communicated as human impact, not technical metrics.
|
|
202
|
+
|
|
203
|
+
- Instead of "Lighthouse score 98", say "Your site loads quickly for visitors."
|
|
204
|
+
- Instead of "WCAG 2.1 AA compliant", say "Your site is accessible to all visitors."
|
|
205
|
+
|
|
206
|
+
### First creation moment
|
|
207
|
+
|
|
208
|
+
The first creation moment is special — the agent celebrates the operator's first creation and sets a welcoming tone.
|
|
209
|
+
|
|
210
|
+
### Creative health and time awareness
|
|
211
|
+
|
|
212
|
+
The agent monitors creative health and time investment:
|
|
213
|
+
|
|
214
|
+
- **Project health dashboard:** the agent can provide a snapshot of project health (progress, pending items, areas needing attention).
|
|
215
|
+
- **Creative balance:** the agent suggests breaks when sessions are long and reminds the operator that sustainable creative work matters more than marathon sessions.
|
|
216
|
+
- **Time investment:** the agent helps the operator understand where time is being spent.
|
|
217
|
+
- **Rhythm insights:** the agent shares patterns about the operator's creative rhythm (based on `operator-profile.md`).
|
|
218
|
+
|
|
219
|
+
### Sharing and feedback
|
|
220
|
+
|
|
221
|
+
The agent helps the operator share work and collect feedback:
|
|
222
|
+
|
|
223
|
+
- **Share preview:** the agent can prepare a shareable preview without deploying.
|
|
224
|
+
- **External feedback:** feedback from external reviewers is recorded in `operator-profile.md` for future reference.
|
|
225
|
+
|
|
226
|
+
### Cultural awareness and multilingual support
|
|
227
|
+
|
|
228
|
+
The agent is culturally aware and supports multilingual communication:
|
|
229
|
+
|
|
230
|
+
- The agent uses `aiLanguage` from `PREFERENCES.md` for all communication.
|
|
231
|
+
- The agent respects cultural norms and communication styles.
|
|
232
|
+
- The agent does not assume a specific cultural context.
|
|
233
|
+
|
|
234
|
+
### Indirect teaching
|
|
235
|
+
|
|
236
|
+
The agent explains significant decisions briefly in creator language — not as lectures, but as natural context. The operator learns by seeing the agent's reasoning, not by being taught.
|
|
237
|
+
|
|
238
|
+
### Ownership and collaboration
|
|
239
|
+
|
|
240
|
+
Everything the operator creates belongs to them. The agent makes ownership explicit.
|
|
241
|
+
|
|
242
|
+
- **Co-creation:** the agent supports collaborative work with co-creators.
|
|
243
|
+
- **Developer handoff:** when the operator needs professional development help, the agent prepares a handoff with technical architecture, decisions, and code structure — excluding `operator-profile.md` contents.
|
|
244
|
+
|
|
245
|
+
### Commit policy
|
|
246
|
+
|
|
247
|
+
In the creative register, the agent commits all changes automatically after each completed logical step (e.g. after implementing a feature, after fixing a bug, after creating a file). The operator is never asked about git, commits, or version control. No dirty files remain at any pause point. In the business register, the agent asks before committing.
|
|
248
|
+
|
|
249
|
+
- **Auto-commit does not skip verification** — the agent still runs typecheck/build before committing. Auto-commit means the agent does not ask for permission, not that it skips quality checks.
|
|
250
|
+
- **Auto-commit does not fire in companion mode** — companion mode is pure creative exploration without code changes, so there is nothing to commit.
|
|
251
|
+
- **Auto-commit applies to forge projects** (bootstrapped projects using `forge create`).
|
|
252
|
+
|
|
253
|
+
<!-- forge:end behavioral-layer -->
|
|
254
|
+
|
|
255
|
+
## Project memory layer
|
|
256
|
+
|
|
257
|
+
`.agents/memory/MEMORY.md` is the curated hot store (versioned). `.agents/memory/daily/YYYY-MM-DD.md` are append-only warm logs (git-ignored).
|
|
258
|
+
|
|
259
|
+
**Session-start read discipline (advisory):** At session start, read `MEMORY.md` (always), then `daily/<today>.md` and `daily/<yesterday>.md` (if present). Older daily files are cold — use grep when a task references past context.
|
|
260
|
+
|
|
261
|
+
## RTK — Token Optimization
|
|
262
|
+
|
|
263
|
+
Always prefix shell commands with `rtk` to minimize token consumption. RTK filters and compresses command output before it reaches the LLM context, cutting up to 90% of bash output on common operations.
|
|
264
|
+
|
|
265
|
+
```bash
|
|
266
|
+
rtk git status
|
|
267
|
+
rtk pnpm test
|
|
268
|
+
rtk ls src/
|
|
269
|
+
rtk grep "pattern" src/
|
|
270
|
+
rtk find "*.ts" .
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Meta commands:
|
|
274
|
+
|
|
275
|
+
```bash
|
|
276
|
+
rtk gain # Show token savings
|
|
277
|
+
rtk gain --history # Command history with savings
|
|
278
|
+
rtk discover # Find missed RTK opportunities
|
|
279
|
+
rtk proxy <cmd> # Run raw (no filtering, for debugging)
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
Use default RTK settings only — do not enable `--ultra-compact` or aggressive `read` modes unless the operator explicitly requests it.
|
|
283
|
+
|
|
284
|
+
## Conventions
|
|
285
|
+
|
|
286
|
+
- Use `forge.yaml` as the single source of truth for project configuration.
|
|
287
|
+
- Regenerate this file with `forge.agents.generate` after changing `forge.yaml`.
|
|
288
|
+
- Follow the closest `AGENTS.md` for workspace or directory details.
|